1 #include "Empire.h"
2
3 #include "../util/i18n.h"
4 #include "../util/Random.h"
5 #include "../util/Logger.h"
6 #include "../util/AppInterface.h"
7 #include "../util/SitRepEntry.h"
8 #include "../universe/Building.h"
9 #include "../universe/BuildingType.h"
10 #include "../universe/Fleet.h"
11 #include "../universe/Ship.h"
12 #include "../universe/ShipDesign.h"
13 #include "../universe/ShipHull.h"
14 #include "../universe/ShipPart.h"
15 #include "../universe/Planet.h"
16 #include "../universe/System.h"
17 #include "../universe/Tech.h"
18 #include "../universe/UniverseObject.h"
19 #include "../universe/UnlockableItem.h"
20 #include "EmpireManager.h"
21 #include "Supply.h"
22
23 #include <boost/uuid/uuid_io.hpp>
24 #include <unordered_set>
25
26
27 namespace {
28 const float EPSILON = 0.01f;
29 const std::string EMPTY_STRING;
30
31 DeclareThreadSafeLogger(supply);
32 }
33
34
35 ////////////
36 // Empire //
37 ////////////
Empire()38 Empire::Empire() :
39 m_authenticated(false),
40 m_research_queue(m_id),
41 m_production_queue(m_id)
42 { Init(); }
43
Empire(const std::string & name,const std::string & player_name,int empire_id,const GG::Clr & color,bool authenticated)44 Empire::Empire(const std::string& name, const std::string& player_name,
45 int empire_id, const GG::Clr& color, bool authenticated) :
46 m_id(empire_id),
47 m_name(name),
48 m_player_name(player_name),
49 m_authenticated(authenticated),
50 m_color(color),
51 m_research_queue(m_id),
52 m_production_queue(m_id)
53 {
54 DebugLogger() << "Empire::Empire(" << name << ", " << player_name << ", " << empire_id << ", colour)";
55 Init();
56 }
57
Init()58 void Empire::Init() {
59 m_resource_pools[RE_RESEARCH] = std::make_shared<ResourcePool>(RE_RESEARCH);
60 m_resource_pools[RE_INDUSTRY] = std::make_shared<ResourcePool>(RE_INDUSTRY);
61 m_resource_pools[RE_TRADE] = std::make_shared<ResourcePool>(RE_TRADE);
62
63 m_eliminated = false;
64
65 m_meters[UserStringNop("METER_DETECTION_STRENGTH")];
66 m_meters[UserStringNop("METER_BUILDING_COST_FACTOR")];
67 m_meters[UserStringNop("METER_SHIP_COST_FACTOR")];
68 m_meters[UserStringNop("METER_TECH_COST_FACTOR")];
69 }
70
~Empire()71 Empire::~Empire()
72 { ClearSitRep(); }
73
Name() const74 const std::string& Empire::Name() const
75 { return m_name; }
76
PlayerName() const77 const std::string& Empire::PlayerName() const
78 { return m_player_name; }
79
IsAuthenticated() const80 bool Empire::IsAuthenticated() const
81 { return m_authenticated; }
82
EmpireID() const83 int Empire::EmpireID() const
84 { return m_id; }
85
Color() const86 const GG::Clr& Empire::Color() const
87 { return m_color; }
88
CapitalID() const89 int Empire::CapitalID() const
90 { return m_capital_id; }
91
SourceID() const92 int Empire::SourceID() const {
93 auto good_source = Source();
94 return good_source ? good_source->ID() : INVALID_OBJECT_ID;
95 }
96
Source() const97 std::shared_ptr<const UniverseObject> Empire::Source() const {
98 if (m_eliminated)
99 return nullptr;
100
101 // Use the current source if valid
102 auto valid_current_source = Objects().get(m_source_id);
103 if (valid_current_source && valid_current_source->OwnedBy(m_id))
104 return valid_current_source;
105
106 // Try the capital
107 auto capital_as_source = Objects().get(m_capital_id);
108 if (capital_as_source && capital_as_source->OwnedBy(m_id)) {
109 m_source_id = m_capital_id;
110 return capital_as_source;
111 }
112
113 // Find any object owned by the empire
114 // TODO determine if ExistingObjects() is faster and acceptable
115 for (const auto& obj : Objects().all()) {
116 if (obj->OwnedBy(m_id)) {
117 m_source_id = obj->ID();
118 return obj;
119 }
120 }
121
122 m_source_id = INVALID_OBJECT_ID;
123 return nullptr;
124 }
125
Dump() const126 std::string Empire::Dump() const {
127 std::string retval = "Empire name: " + m_name +
128 " ID: " + std::to_string(m_id) +
129 " Capital ID: " + std::to_string(m_capital_id);
130 retval += " meters:\n";
131 for (const auto& meter : m_meters) {
132 retval += UserString(meter.first) + ": " +
133 std::to_string(meter.second.Initial()) + "\n";
134 }
135 return retval;
136 }
137
SetCapitalID(int id)138 void Empire::SetCapitalID(int id) {
139 m_capital_id = INVALID_OBJECT_ID;
140 m_source_id = INVALID_OBJECT_ID;
141
142 if (id == INVALID_OBJECT_ID)
143 return;
144
145 // Verify that the capital exists and is owned by the empire
146 auto possible_capital = Objects().ExistingObject(id);
147 if (possible_capital && possible_capital->OwnedBy(m_id))
148 m_capital_id = id;
149
150 auto possible_source = Objects().get(id);
151 if (possible_source && possible_source->OwnedBy(m_id))
152 m_source_id = id;
153 }
154
GetMeter(const std::string & name)155 Meter* Empire::GetMeter(const std::string& name) {
156 auto it = m_meters.find(name);
157 if (it != m_meters.end())
158 return &(it->second);
159 else
160 return nullptr;
161 }
162
GetMeter(const std::string & name) const163 const Meter* Empire::GetMeter(const std::string& name) const {
164 auto it = m_meters.find(name);
165 if (it != m_meters.end())
166 return &(it->second);
167 else
168 return nullptr;
169 }
170
BackPropagateMeters()171 void Empire::BackPropagateMeters() {
172 for (auto& meter : m_meters)
173 meter.second.BackPropagate();
174 }
175
ResearchableTech(const std::string & name) const176 bool Empire::ResearchableTech(const std::string& name) const {
177 const Tech* tech = GetTech(name);
178 if (!tech)
179 return false;
180 for (const auto& prereq : tech->Prerequisites()) {
181 if (!m_techs.count(prereq))
182 return false;
183 }
184 return true;
185 }
186
HasResearchedPrereqAndUnresearchedPrereq(const std::string & name) const187 bool Empire::HasResearchedPrereqAndUnresearchedPrereq(const std::string& name) const {
188 const Tech* tech = GetTech(name);
189 if (!tech)
190 return false;
191 bool one_unresearched = false;
192 bool one_researched = false;
193 for (const auto& prereq : tech->Prerequisites()) {
194 if (m_techs.count(prereq))
195 one_researched = true;
196 else
197 one_unresearched = true;
198 }
199 return one_unresearched && one_researched;
200 }
201
GetResearchQueue() const202 const ResearchQueue& Empire::GetResearchQueue() const
203 { return m_research_queue; }
204
ResearchProgress(const std::string & name) const205 float Empire::ResearchProgress(const std::string& name) const {
206 auto it = m_research_progress.find(name);
207 if (it == m_research_progress.end())
208 return 0.0f;
209 const Tech* tech = GetTech(it->first);
210 if (!tech)
211 return 0.0f;
212 float tech_cost = tech->ResearchCost(m_id);
213 return it->second * tech_cost;
214 }
215
ResearchedTechs() const216 const std::map<std::string, int>& Empire::ResearchedTechs() const
217 { return m_techs; }
218
TechResearched(const std::string & name) const219 bool Empire::TechResearched(const std::string& name) const
220 { return m_techs.count(name); }
221
GetTechStatus(const std::string & name) const222 TechStatus Empire::GetTechStatus(const std::string& name) const {
223 if (TechResearched(name)) return TS_COMPLETE;
224 if (ResearchableTech(name)) return TS_RESEARCHABLE;
225 if (HasResearchedPrereqAndUnresearchedPrereq(name)) return TS_HAS_RESEARCHED_PREREQ;
226 return TS_UNRESEARCHABLE;
227 }
228
TopPriorityEnqueuedTech() const229 const std::string& Empire::TopPriorityEnqueuedTech() const {
230 if (m_research_queue.empty())
231 return EMPTY_STRING;
232 auto it = m_research_queue.begin();
233 const std::string& tech = it->name;
234 return tech;
235 }
236
MostExpensiveEnqueuedTech() const237 const std::string& Empire::MostExpensiveEnqueuedTech() const {
238 if (m_research_queue.empty())
239 return EMPTY_STRING;
240 float biggest_cost = -99999.9f; // arbitrary small number
241
242 const ResearchQueue::Element* best_elem = nullptr;
243
244 for (const auto& elem : m_research_queue) {
245 const Tech* tech = GetTech(elem.name);
246 if (!tech)
247 continue;
248 float tech_cost = tech->ResearchCost(m_id);
249 if (tech_cost > biggest_cost) {
250 biggest_cost = tech_cost;
251 best_elem = &elem;
252 }
253 }
254
255 if (best_elem)
256 return best_elem->name;
257 return EMPTY_STRING;
258 }
259
LeastExpensiveEnqueuedTech() const260 const std::string& Empire::LeastExpensiveEnqueuedTech() const {
261 if (m_research_queue.empty())
262 return EMPTY_STRING;
263 float smallest_cost = 999999.9f; // arbitrary large number
264
265 const ResearchQueue::Element* best_elem = nullptr;
266
267 for (const auto& elem : m_research_queue) {
268 const Tech* tech = GetTech(elem.name);
269 if (!tech)
270 continue;
271 float tech_cost = tech->ResearchCost(m_id);
272 if (tech_cost < smallest_cost) {
273 smallest_cost = tech_cost;
274 best_elem = &elem;
275 }
276 }
277
278 if (best_elem)
279 return best_elem->name;
280 return EMPTY_STRING;
281 }
282
MostRPSpentEnqueuedTech() const283 const std::string& Empire::MostRPSpentEnqueuedTech() const {
284 float most_spent = -999999.9f; // arbitrary small number
285 const std::map<std::string, float>::value_type* best_progress = nullptr;
286
287 for (const auto& progress : m_research_progress) {
288 const auto& tech_name = progress.first;
289 if (!m_research_queue.InQueue(tech_name))
290 continue;
291 float rp_spent = progress.second;
292 if (rp_spent > most_spent) {
293 best_progress = &progress;
294 most_spent = rp_spent;
295 }
296 }
297
298 if (best_progress)
299 return best_progress->first;
300 return EMPTY_STRING;
301 }
302
MostRPCostLeftEnqueuedTech() const303 const std::string& Empire::MostRPCostLeftEnqueuedTech() const {
304 float most_left = -999999.9f; // arbitrary small number
305 const std::map<std::string, float>::value_type* best_progress = nullptr;
306
307 for (const auto& progress : m_research_progress) {
308 const auto& tech_name = progress.first;
309 const Tech* tech = GetTech(tech_name);
310 if (!tech)
311 continue;
312
313 if (!m_research_queue.InQueue(tech_name))
314 continue;
315
316 float rp_spent = progress.second;
317 float rp_total_cost = tech->ResearchCost(m_id);
318 float rp_left = std::max(0.0f, rp_total_cost - rp_spent);
319
320 if (rp_left > most_left) {
321 best_progress = &progress;
322 most_left = rp_left;
323 }
324 }
325
326 if (best_progress)
327 return best_progress->first;
328 return EMPTY_STRING;
329 }
330
TopPriorityResearchableTech() const331 const std::string& Empire::TopPriorityResearchableTech() const {
332 if (m_research_queue.empty())
333 return EMPTY_STRING;
334 for (const auto& elem : m_research_queue) {
335 if (this->ResearchableTech(elem.name))
336 return elem.name;
337 }
338 return EMPTY_STRING;
339 }
340
MostExpensiveResearchableTech() const341 const std::string& Empire::MostExpensiveResearchableTech() const {
342 return EMPTY_STRING; // TODO: IMPLEMENT THIS
343 }
344
LeastExpensiveResearchableTech() const345 const std::string& Empire::LeastExpensiveResearchableTech() const {
346 return EMPTY_STRING; // TODO: IMPLEMENT THIS
347 }
348
MostRPSpentResearchableTech() const349 const std::string& Empire::MostRPSpentResearchableTech() const {
350 return EMPTY_STRING; // TODO: IMPLEMENT THIS
351 }
352
MostRPCostLeftResearchableTech() const353 const std::string& Empire::MostRPCostLeftResearchableTech() const {
354 return EMPTY_STRING; // TODO: IMPLEMENT THIS
355 }
356
AvailableBuildingTypes() const357 const std::set<std::string>& Empire::AvailableBuildingTypes() const
358 { return m_available_building_types; }
359
BuildingTypeAvailable(const std::string & name) const360 bool Empire::BuildingTypeAvailable(const std::string& name) const
361 { return m_available_building_types.count(name); }
362
ShipDesigns() const363 const std::set<int>& Empire::ShipDesigns() const
364 { return m_known_ship_designs; }
365
AvailableShipDesigns() const366 std::set<int> Empire::AvailableShipDesigns() const {
367 // create new map containing all ship designs that are available
368 std::set<int> retval;
369 for (int design_id : m_known_ship_designs) {
370 if (ShipDesignAvailable(design_id))
371 retval.insert(design_id);
372 }
373 return retval;
374 }
375
ShipDesignAvailable(int ship_design_id) const376 bool Empire::ShipDesignAvailable(int ship_design_id) const {
377 const ShipDesign* design = GetShipDesign(ship_design_id);
378 return design ? ShipDesignAvailable(*design) : false;
379 }
380
ShipDesignAvailable(const ShipDesign & design) const381 bool Empire::ShipDesignAvailable(const ShipDesign& design) const {
382 if (!design.Producible()) return false;
383
384 // design is kept, but still need to verify that it is buildable at this time. Part or hull tech
385 // requirements might prevent it from being built.
386 for (const auto& name : design.Parts()) {
387 if (name.empty())
388 continue; // empty slot can't be unavailable
389 if (!ShipPartAvailable(name))
390 return false;
391 }
392 if (!ShipHullAvailable(design.Hull()))
393 return false;
394
395 // if there are no reasons the design isn't available, then by default it is available
396 return true;
397 }
398
ShipDesignKept(int ship_design_id) const399 bool Empire::ShipDesignKept(int ship_design_id) const
400 { return m_known_ship_designs.count(ship_design_id); }
401
AvailableShipParts() const402 const std::set<std::string>& Empire::AvailableShipParts() const
403 { return m_available_ship_parts; }
404
ShipPartAvailable(const std::string & name) const405 bool Empire::ShipPartAvailable(const std::string& name) const
406 { return m_available_ship_parts.count(name); }
407
AvailableShipHulls() const408 const std::set<std::string>& Empire::AvailableShipHulls() const
409 { return m_available_ship_hulls; }
410
ShipHullAvailable(const std::string & name) const411 bool Empire::ShipHullAvailable(const std::string& name) const
412 { return m_available_ship_hulls.count(name); }
413
GetProductionQueue() const414 const ProductionQueue& Empire::GetProductionQueue() const
415 { return m_production_queue; }
416
ProductionStatus(int i) const417 float Empire::ProductionStatus(int i) const {
418 if (0 > i || i >= static_cast<int>(m_production_queue.size()))
419 return -1.0f;
420 float item_progress = m_production_queue[i].progress;
421 float item_cost;
422 int item_time;
423 std::tie(item_cost, item_time) = this->ProductionCostAndTime(m_production_queue[i]);
424 return item_progress * item_cost * m_production_queue[i].blocksize;
425 }
426
ProductionCostAndTime(const ProductionQueue::Element & element) const427 std::pair<float, int> Empire::ProductionCostAndTime(const ProductionQueue::Element& element) const
428 { return ProductionCostAndTime(element.item, element.location); }
429
ProductionCostAndTime(const ProductionQueue::ProductionItem & item,int location_id) const430 std::pair<float, int> Empire::ProductionCostAndTime(const ProductionQueue::ProductionItem& item,
431 int location_id) const
432 {
433 if (item.build_type == BT_BUILDING) {
434 const BuildingType* type = GetBuildingType(item.name);
435 if (!type)
436 return std::make_pair(-1.0, -1);
437 return std::make_pair(type->ProductionCost(m_id, location_id),
438 type->ProductionTime(m_id, location_id));
439
440 } else if (item.build_type == BT_SHIP) {
441 const ShipDesign* design = GetShipDesign(item.design_id);
442 if (design)
443 return std::make_pair(design->ProductionCost(m_id, location_id),
444 design->ProductionTime(m_id, location_id));
445 return std::make_pair(-1.0, -1);
446
447 } else if (item.build_type == BT_STOCKPILE) {
448 return std::make_pair(1.0, 1);
449 }
450 ErrorLogger() << "Empire::ProductionCostAndTime was passed a ProductionItem with an invalid BuildType";
451 return std::make_pair(-1.0, -1);
452 }
453
HasExploredSystem(int ID) const454 bool Empire::HasExploredSystem(int ID) const
455 { return m_explored_systems.count(ID); }
456
ProducibleItem(BuildType build_type,int location_id) const457 bool Empire::ProducibleItem(BuildType build_type, int location_id) const {
458 if (build_type == BT_SHIP)
459 throw std::invalid_argument("Empire::ProducibleItem was passed BuildType BT_SHIP with no further parameters, but ship designs are tracked by number");
460
461 if (build_type == BT_BUILDING)
462 throw std::invalid_argument("Empire::ProducibleItem was passed BuildType BT_BUILDING with no further parameters, but buildings are tracked by name");
463
464 if (location_id == INVALID_OBJECT_ID)
465 return false;
466
467 // must own the production location...
468 auto location = Objects().get(location_id);
469 if (!location) {
470 WarnLogger() << "Empire::ProducibleItem for BT_STOCKPILE unable to get location object with id " << location_id;
471 return false;
472 }
473
474 if (!location->OwnedBy(m_id))
475 return false;
476
477 if (!std::dynamic_pointer_cast<const ResourceCenter>(location))
478 return false;
479
480 if (build_type == BT_STOCKPILE) {
481 return true;
482
483 } else {
484 ErrorLogger() << "Empire::ProducibleItem was passed an invalid BuildType";
485 return false;
486 }
487
488 }
489
ProducibleItem(BuildType build_type,const std::string & name,int location) const490 bool Empire::ProducibleItem(BuildType build_type, const std::string& name, int location) const {
491 // special case to check for ships being passed with names, not design ids
492 if (build_type == BT_SHIP)
493 throw std::invalid_argument("Empire::ProducibleItem was passed BuildType BT_SHIP with a name, but ship designs are tracked by number");
494
495 if (build_type == BT_STOCKPILE)
496 throw std::invalid_argument("Empire::ProducibleItem was passed BuildType BT_STOCKPILE with a name, but the stockpile does not need an identification");
497
498 if (build_type == BT_BUILDING && !BuildingTypeAvailable(name))
499 return false;
500
501 const auto* building_type = GetBuildingType(name);
502 if (!building_type || !building_type->Producible())
503 return false;
504
505 auto build_location = Objects().get(location);
506 if (!build_location)
507 return false;
508
509 if (build_type == BT_BUILDING) {
510 // specified location must be a valid production location for that building type
511 return building_type->ProductionLocation(m_id, location);
512
513 } else {
514 ErrorLogger() << "Empire::ProducibleItem was passed an invalid BuildType";
515 return false;
516 }
517 }
518
ProducibleItem(BuildType build_type,int design_id,int location) const519 bool Empire::ProducibleItem(BuildType build_type, int design_id, int location) const {
520 // special case to check for buildings being passed with ids, not names
521 if (build_type == BT_BUILDING)
522 throw std::invalid_argument("Empire::ProducibleItem was passed BuildType BT_BUILDING with a design id number, but buildings are tracked by name");
523
524 if (build_type == BT_STOCKPILE)
525 throw std::invalid_argument("Empire::ProducibleItem was passed BuildType BT_STOCKPILE with a design id, but the stockpile does not need an identification");
526
527 if (build_type == BT_SHIP && !ShipDesignAvailable(design_id))
528 return false;
529
530 // design must be known to this empire
531 const ShipDesign* ship_design = GetShipDesign(design_id);
532 if (!ship_design || !ship_design->Producible())
533 return false;
534
535 auto build_location = Objects().get(location);
536 if (!build_location) return false;
537
538 if (build_type == BT_SHIP) {
539 // specified location must be a valid production location for this design
540 return ship_design->ProductionLocation(m_id, location);
541
542 } else {
543 ErrorLogger() << "Empire::ProducibleItem was passed an invalid BuildType";
544 return false;
545 }
546 }
547
ProducibleItem(const ProductionQueue::ProductionItem & item,int location) const548 bool Empire::ProducibleItem(const ProductionQueue::ProductionItem& item, int location) const {
549 if (item.build_type == BT_BUILDING)
550 return ProducibleItem(item.build_type, item.name, location);
551 else if (item.build_type == BT_SHIP)
552 return ProducibleItem(item.build_type, item.design_id, location);
553 else if (item.build_type == BT_STOCKPILE)
554 return ProducibleItem(item.build_type, location);
555 else
556 throw std::invalid_argument("Empire::ProducibleItem was passed a ProductionItem with an invalid BuildType");
557 return false;
558 }
559
EnqueuableItem(BuildType build_type,const std::string & name,int location) const560 bool Empire::EnqueuableItem(BuildType build_type, const std::string& name, int location) const {
561 if (build_type != BT_BUILDING)
562 return false;
563
564 const auto* building_type = GetBuildingType(name);
565 if (!building_type || !building_type->Producible())
566 return false;
567
568 auto build_location = Objects().get(location);
569 if (!build_location)
570 return false;
571
572 // specified location must be a valid production location for that building type
573 return building_type->EnqueueLocation(m_id, location);
574 }
575
EnqueuableItem(const ProductionQueue::ProductionItem & item,int location) const576 bool Empire::EnqueuableItem(const ProductionQueue::ProductionItem& item, int location) const {
577 if (item.build_type == BT_BUILDING)
578 return EnqueuableItem(item.build_type, item.name, location);
579 else if (item.build_type == BT_SHIP) // ships don't have a distinction between enqueuable and producible
580 return ProducibleItem(item.build_type, item.design_id, location);
581 else if (item.build_type == BT_STOCKPILE) // stockpile does not have a distinction between enqueuable and producible
582 return ProducibleItem(item.build_type, location);
583 else
584 throw std::invalid_argument("Empire::ProducibleItem was passed a ProductionItem with an invalid BuildType");
585 return false;
586 }
587
588
NumSitRepEntries(int turn) const589 int Empire::NumSitRepEntries(int turn/* = INVALID_GAME_TURN*/) const {
590 if (turn == INVALID_GAME_TURN)
591 return m_sitrep_entries.size();
592 int count = 0;
593 for (const SitRepEntry& sitrep : m_sitrep_entries)
594 if (sitrep.GetTurn() == turn)
595 count++;
596 return count;
597 }
598
Eliminated() const599 bool Empire::Eliminated() const {
600 return m_eliminated;
601 }
602
Eliminate()603 void Empire::Eliminate() {
604 m_eliminated = true;
605
606 for (auto& entry : Empires())
607 entry.second->AddSitRepEntry(CreateEmpireEliminatedSitRep(EmpireID()));
608
609 // some Empire data not cleared when eliminating since it might be useful
610 // to remember later, and having it doesn't hurt anything (as opposed to
611 // the production queue that might actually cause some problems if left
612 // uncleared after elimination
613
614 m_capital_id = INVALID_OBJECT_ID;
615 // m_newly_researched_techs
616 // m_techs
617 m_research_queue.clear();
618 m_research_progress.clear();
619 m_production_queue.clear();
620 // m_available_building_types;
621 // m_available_ship_parts;
622 // m_available_ship_hulls;
623 // m_explored_systems;
624 // m_known_ship_designs;
625 m_sitrep_entries.clear();
626 for (auto& entry : m_resource_pools)
627 entry.second->SetObjects(std::vector<int>());
628 m_population_pool.SetPopCenters(std::vector<int>());
629
630 // m_ship_names_used;
631 m_supply_system_ranges.clear();
632 m_supply_unobstructed_systems.clear();
633 }
634
Won() const635 bool Empire::Won() const {
636 return !m_victories.empty();
637 }
638
Win(const std::string & reason)639 void Empire::Win(const std::string& reason) {
640 if (m_victories.insert(reason).second) {
641 for (auto& entry : Empires()) {
642 entry.second->AddSitRepEntry(CreateVictorySitRep(reason, EmpireID()));
643 }
644 }
645 }
646
Ready() const647 bool Empire::Ready() const
648 { return m_ready; }
649
SetReady(bool ready)650 void Empire::SetReady(bool ready)
651 { m_ready = ready; }
652
UpdateSystemSupplyRanges(const std::set<int> & known_objects)653 void Empire::UpdateSystemSupplyRanges(const std::set<int>& known_objects) {
654 //std::cout << "Empire::UpdateSystemSupplyRanges() for empire " << this->Name() << std::endl;
655 m_supply_system_ranges.clear();
656
657 // as of this writing, only planets can generate supply propagation
658 std::vector<std::shared_ptr<const UniverseObject>> owned_planets;
659 for (const auto& planet: Objects().find<Planet>(known_objects)) {
660 if (!planet)
661 continue;
662 if (planet->OwnedBy(this->EmpireID()))
663 owned_planets.push_back(planet);
664 }
665
666 //std::cout << "... empire owns " << owned_planets.size() << " planets" << std::endl;
667 for (auto& obj : owned_planets) {
668 //std::cout << "... considering owned planet: " << obj->Name() << std::endl;
669
670 // ensure object is within a system, from which it can distribute supplies
671 int system_id = obj->SystemID();
672 if (system_id == INVALID_OBJECT_ID)
673 continue; // TODO: consider future special case if current object is itself a system
674
675 // check if object has a supply meter
676 if (obj->GetMeter(METER_SUPPLY)) {
677 // get resource supply range for next turn for this object
678 float supply_range = obj->GetMeter(METER_SUPPLY)->Initial();
679
680 // if this object can provide more supply range than the best previously checked object in this system, record its range as the new best for the system
681 auto system_it = m_supply_system_ranges.find(system_id); // try to find a previous entry for this system's supply range
682 if (system_it == m_supply_system_ranges.end() || supply_range > system_it->second) {// if there is no previous entry, or the previous entry is shorter than the new one, add or replace the entry
683 //std::cout << " ... object " << obj->Name() << " has resource supply range: " << resource_supply_range << std::endl;
684 m_supply_system_ranges[system_id] = supply_range;
685 }
686 }
687 }
688 }
689
UpdateSystemSupplyRanges()690 void Empire::UpdateSystemSupplyRanges() {
691 const Universe& universe = GetUniverse();
692 const ObjectMap& empire_known_objects = EmpireKnownObjects(this->EmpireID());
693
694 // get ids of objects partially or better visible to this empire.
695 const std::set<int>& known_destroyed_objects = universe.EmpireKnownDestroyedObjectIDs(this->EmpireID());
696
697 std::set<int> known_objects_set;
698
699 // exclude objects known to have been destroyed (or rather, include ones that aren't known by this empire to be destroyed)
700 for (const auto& obj : empire_known_objects.all())
701 if (!known_destroyed_objects.count(obj->ID()))
702 known_objects_set.insert(obj->ID());
703 UpdateSystemSupplyRanges(known_objects_set);
704 }
705
UpdateUnobstructedFleets()706 void Empire::UpdateUnobstructedFleets() {
707 const std::set<int>& known_destroyed_objects =
708 GetUniverse().EmpireKnownDestroyedObjectIDs(this->EmpireID());
709
710 for (const auto& system : Objects().find<System>(m_supply_unobstructed_systems)) {
711 if (!system)
712 continue;
713
714 for (auto& fleet : Objects().find<Fleet>(system->FleetIDs())) {
715 if (known_destroyed_objects.count(fleet->ID()))
716 continue;
717 if (fleet->OwnedBy(m_id))
718 fleet->SetArrivalStarlane(system->ID());
719 }
720 }
721 }
722
UpdateSupplyUnobstructedSystems(bool precombat)723 void Empire::UpdateSupplyUnobstructedSystems(bool precombat /*=false*/) {
724 Universe& universe = GetUniverse();
725
726 // get ids of systems partially or better visible to this empire.
727 // TODO: make a UniverseObjectVisitor for objects visible to an empire at a specified visibility or greater
728 const std::set<int>& known_destroyed_objects = universe.EmpireKnownDestroyedObjectIDs(this->EmpireID());
729
730 std::set<int> known_systems_set;
731
732 // exclude systems known to have been destroyed (or rather, include ones that aren't known to be destroyed)
733 for (const auto& sys : EmpireKnownObjects(this->EmpireID()).all<System>())
734 if (!known_destroyed_objects.count(sys->ID()))
735 known_systems_set.insert(sys->ID());
736 UpdateSupplyUnobstructedSystems(known_systems_set, precombat);
737 }
738
UpdateSupplyUnobstructedSystems(const std::set<int> & known_systems,bool precombat)739 void Empire::UpdateSupplyUnobstructedSystems(const std::set<int>& known_systems, bool precombat /*=false*/) {
740 TraceLogger(supply) << "UpdateSupplyUnobstructedSystems (allowing supply propagation) for empire " << m_id;
741 m_supply_unobstructed_systems.clear();
742
743 // get systems with historically at least partial visibility
744 std::set<int> systems_with_at_least_partial_visibility_at_some_point;
745 for (int system_id : known_systems) {
746 const auto& vis_turns = GetUniverse().GetObjectVisibilityTurnMapByEmpire(system_id, m_id);
747 if (vis_turns.count(VIS_PARTIAL_VISIBILITY))
748 systems_with_at_least_partial_visibility_at_some_point.insert(system_id);
749 }
750
751 // get all fleets, or just those visible to this client's empire
752 const auto& known_destroyed_objects = GetUniverse().EmpireKnownDestroyedObjectIDs(this->EmpireID());
753
754 // get empire supply ranges
755 std::map<int, std::map<int, float>> empire_system_supply_ranges;
756 for (const auto& entry : Empires()) {
757 const Empire* empire = entry.second;
758 empire_system_supply_ranges[entry.first] = empire->SystemSupplyRanges();
759 }
760
761 // find systems that contain fleets that can either maintain supply or block supply.
762 // to affect supply in either manner, a fleet must be armed & aggressive, & must be not
763 // trying to depart the systme. Qualifying enemy fleets will blockade if no friendly fleets
764 // are present, or if the friendly fleets were already blockade-restricted and the enemy
765 // fleets were not (meaning that the enemy fleets were continuing an existing blockade)
766 // Friendly fleets can preserve available starlane accesss even if they are trying to leave the system
767
768 // Unrestricted lane access (i.e, (fleet->ArrivalStarlane() == system->ID()) ) is used as a proxy for
769 // order of arrival -- if an enemy has unrestricted lane access and you don't, they must have arrived
770 // before you, or be in cahoots with someone who did.
771 std::set<int> systems_containing_friendly_fleets;
772 std::set<int> systems_with_lane_preserving_fleets;
773 std::set<int> unrestricted_friendly_systems;
774 std::set<int> systems_containing_obstructing_objects;
775 std::set<int> unrestricted_obstruction_systems;
776 for (auto& fleet : GetUniverse().Objects().all<Fleet>()) {
777 int system_id = fleet->SystemID();
778 if (system_id == INVALID_OBJECT_ID) {
779 continue; // not in a system, so can't affect system obstruction
780 } else if (known_destroyed_objects.count(fleet->ID())) {
781 continue; //known to be destroyed so can't affect supply, important just in case being updated on client side
782 }
783
784 TraceLogger(supply) << "Fleet " << fleet->ID() << " is in system " << system_id << " with next system " << fleet->NextSystemID() << " and is owned by " << fleet->Owner() << " armed: " << fleet->HasArmedShips() << " and agressive: " << fleet->Aggressive();
785 if (fleet->HasArmedShips() && fleet->Aggressive()) {
786 if (fleet->OwnedBy(m_id)) {
787 if (fleet->NextSystemID() == INVALID_OBJECT_ID || fleet->NextSystemID() == fleet->SystemID()) {
788 systems_containing_friendly_fleets.insert(system_id);
789 if (fleet->ArrivalStarlane() == system_id)
790 unrestricted_friendly_systems.insert(system_id);
791 else
792 systems_with_lane_preserving_fleets.insert(system_id);
793 }
794 } else if (fleet->NextSystemID() == INVALID_OBJECT_ID || fleet->NextSystemID() == fleet->SystemID()) {
795 int fleet_owner = fleet->Owner();
796 bool fleet_at_war = fleet_owner == ALL_EMPIRES || Empires().GetDiplomaticStatus(m_id, fleet_owner) == DIPLO_WAR;
797 // newly created ships are not allowed to block supply since they have not even potentially gone
798 // through a combat round at the present location. Potential sources for such new ships are monsters
799 // created via Effect. (Ships/fleets constructed by empires are currently created at a later stage of
800 // turn processing, but even if such were moved forward they should be similarly restricted.) For
801 // checks after combat and prior to turn advancement, we check against age zero here. For checks
802 // after turn advancement but prior to combat we check against age 1. Because the
803 // fleets themselves may be created and/or destroyed purely as organizational matters, we check ship
804 // age not fleet age.
805 int cutoff_age = precombat ? 1 : 0;
806 if (fleet_at_war && fleet->MaxShipAgeInTurns() > cutoff_age) {
807 systems_containing_obstructing_objects.insert(system_id);
808 if (fleet->ArrivalStarlane() == system_id)
809 unrestricted_obstruction_systems.insert(system_id);
810 }
811 }
812 }
813 }
814
815 TraceLogger(supply) << "Empire::UpdateSupplyUnobstructedSystems systems with obstructing objects for empire " << m_id << " : " << [&]() {
816 std::stringstream ss;
817 for (int obj_id : systems_containing_obstructing_objects)
818 { ss << obj_id << ", "; }
819 return ss.str();
820 }();
821
822 DebugLogger() << "Preserved System-Lanes for empire " << m_name << " (" << m_id << ") : " << [&]() {
823 std::stringstream ss2;
824 for (auto sys_lanes : m_preserved_system_exit_lanes) {
825 ss2 << "[Sys: " << sys_lanes.first << " : (";
826 for (auto lane : sys_lanes.second)
827 { ss2 << lane << " "; }
828 ss2 << ")] ";
829 }
830 return ss2.str();
831 }();
832
833 DebugLogger() << "Systems with lane-preserving fleets for empire " << m_name << " (" << m_id << ") : " << [&]() {
834 std::stringstream ss3;
835 for (auto sys_id : systems_with_lane_preserving_fleets)
836 { ss3 << sys_id << ", "; }
837 return ss3.str();
838 }();
839
840
841 // check each potential supplyable system for whether it can propagate supply.
842 for (const auto& sys : Objects().find<System>(known_systems)) {
843 if (!sys)
844 continue;
845
846 // has empire ever seen this system with partial or better visibility?
847 if (!systems_with_at_least_partial_visibility_at_some_point.count(sys->ID())) {
848 TraceLogger(supply) << "System " << sys->Name() << " (" << sys->ID() << ") has never been seen";
849 continue;
850 }
851
852 // if system is explored, then whether it can propagate supply depends
853 // on what friendly / enemy ships and planets are in the system
854
855 if (unrestricted_friendly_systems.count(sys->ID())) {
856 // in unrestricted friendly systems, supply can propagate
857 m_supply_unobstructed_systems.insert(sys->ID());
858 TraceLogger(supply) << "System " << sys->Name() << " (" << sys->ID() << ") +++ is unrestricted and friendly";
859
860 } else if (systems_containing_friendly_fleets.count(sys->ID())) {
861 // if there are unrestricted friendly ships, and no unrestricted enemy fleets, supply can propagate
862 if (!unrestricted_obstruction_systems.count(sys->ID())) {
863 m_supply_unobstructed_systems.insert(sys->ID());
864 TraceLogger(supply) << "System " << sys->Name() << " (" << sys->ID() << ") +++ has friendly fleets and no obstructions";
865 } else {
866 TraceLogger(supply) << "System " << sys->Name() << " (" << sys->ID() << ") --- is has friendly fleets but has obstructions";
867 }
868
869 } else if (!systems_containing_obstructing_objects.count(sys->ID())) {
870 // if there are no friendly fleets or obstructing enemy fleets, supply can propagate
871 m_supply_unobstructed_systems.insert(sys->ID());
872 TraceLogger(supply) << "System " << sys->Name() << " (" << sys->ID() << ") +++ has no obstructing objects";
873
874 } else if (!systems_with_lane_preserving_fleets.count(sys->ID())) {
875 // if there are obstructing enemy fleets but no friendly fleets that could maintain
876 // lane access, supply cannot propagate and this empire's available system exit
877 TraceLogger(supply) << "System " << sys->Name() << " (" << sys->ID() << ") --- has no lane preserving fleets";
878
879 // lanes for this system are cleared
880 if (!m_preserved_system_exit_lanes[sys->ID()].empty()) {
881 std::stringstream ssca;
882 ssca << "Empire::UpdateSupplyUnobstructedSystems clearing preserved lanes for system ("
883 << sys->ID() << "); available lanes were:";
884 for (int system_id : m_preserved_system_exit_lanes[sys->ID()])
885 ssca << system_id << ", ";
886 TraceLogger(supply) << ssca.str();
887 }
888 m_preserved_system_exit_lanes[sys->ID()].clear();
889
890 } else {
891 TraceLogger(supply) << "Empire::UpdateSupplyUnobstructedSystems : Restricted system " << sys->ID() << " with no friendly fleets, no obustrcting enemy fleets, and no lane-preserving fleets";
892 }
893 }
894 }
895
RecordPendingLaneUpdate(int start_system_id,int dest_system_id)896 void Empire::RecordPendingLaneUpdate(int start_system_id, int dest_system_id) {
897 if (!m_supply_unobstructed_systems.count(start_system_id))
898 m_pending_system_exit_lanes[start_system_id].insert(dest_system_id);
899 else { // if the system is unobstructed, mark all its lanes as avilable
900 for (const auto& lane : Objects().get<System>(start_system_id)->StarlanesWormholes()) {
901 m_pending_system_exit_lanes[start_system_id].insert(lane.first); // will add both starlanes and wormholes
902 }
903 }
904 }
905
UpdatePreservedLanes()906 void Empire::UpdatePreservedLanes() {
907 for (auto& system : m_pending_system_exit_lanes) {
908 m_preserved_system_exit_lanes[system.first].insert(system.second.begin(), system.second.end());
909 system.second.clear();
910 }
911 m_pending_system_exit_lanes.clear(); // TODO: consider: not really necessary, & may be more efficient to not clear.
912 }
913
SystemSupplyRanges() const914 const std::map<int, float>& Empire::SystemSupplyRanges() const
915 { return m_supply_system_ranges; }
916
SupplyUnobstructedSystems() const917 const std::set<int>& Empire::SupplyUnobstructedSystems() const
918 { return m_supply_unobstructed_systems; }
919
PreservedLaneTravel(int start_system_id,int dest_system_id) const920 const bool Empire::PreservedLaneTravel(int start_system_id, int dest_system_id) const {
921 auto find_it = m_preserved_system_exit_lanes.find(start_system_id);
922 return find_it != m_preserved_system_exit_lanes.end()
923 && find_it->second.count(dest_system_id);
924 }
925
ExploredSystems() const926 const std::set<int>& Empire::ExploredSystems() const
927 { return m_explored_systems; }
928
KnownStarlanes() const929 const std::map<int, std::set<int>> Empire::KnownStarlanes() const {
930 // compile starlanes leading into or out of each system
931 std::map<int, std::set<int>> retval;
932
933 const Universe& universe = GetUniverse();
934 TraceLogger(supply) << "Empire::KnownStarlanes for empire " << m_id;
935
936 const std::set<int>& known_destroyed_objects = universe.EmpireKnownDestroyedObjectIDs(this->EmpireID());
937 for (const auto& sys : Objects().all<System>())
938 {
939 int start_id = sys->ID();
940 TraceLogger(supply) << "system " << start_id << " has up to " << sys->StarlanesWormholes().size() << " lanes / wormholes";
941
942 // exclude lanes starting at systems known to be destroyed
943 if (known_destroyed_objects.count(start_id)) {
944 TraceLogger(supply) << "system " << start_id << " known destroyed, so lanes from it are unknown";
945 continue;
946 }
947
948 for (const auto& lane : sys->StarlanesWormholes()) {
949 int end_id = lane.first;
950 bool is_wormhole = lane.second;
951 if (is_wormhole || known_destroyed_objects.count(end_id))
952 continue; // is a wormhole, not a starlane, or is connected to a known destroyed system
953 retval[start_id].insert(end_id);
954 retval[end_id].insert(start_id);
955 }
956
957 TraceLogger(supply) << "system " << start_id << " had " << retval[start_id].size() << " known lanes";
958 }
959
960 TraceLogger(supply) << "Total of " << retval.size() << " systems had known lanes";
961 return retval;
962 }
963
VisibleStarlanes() const964 const std::map<int, std::set<int>> Empire::VisibleStarlanes() const {
965 std::map<int, std::set<int>> retval; // compile starlanes leading into or out of each system
966
967 const Universe& universe = GetUniverse();
968 const ObjectMap& objects = universe.Objects();
969
970 for (const auto& sys : objects.all<System>())
971 {
972 int start_id = sys->ID();
973
974 // is system visible to this empire?
975 if (universe.GetObjectVisibilityByEmpire(start_id, m_id) <= VIS_NO_VISIBILITY)
976 continue;
977
978 // get system's visible lanes for this empire
979 for (auto& lane : sys->VisibleStarlanesWormholes(m_id)) {
980 if (lane.second)
981 continue; // is a wormhole, not a starlane
982 int end_id = lane.first;
983 retval[start_id].insert(end_id);
984 retval[end_id].insert(start_id);
985 }
986 }
987
988 return retval;
989 }
990
SitRepBegin() const991 Empire::SitRepItr Empire::SitRepBegin() const
992 { return m_sitrep_entries.begin(); }
993
SitRepEnd() const994 Empire::SitRepItr Empire::SitRepEnd() const
995 { return m_sitrep_entries.end(); }
996
ProductionPoints() const997 float Empire::ProductionPoints() const
998 { return GetResourcePool(RE_INDUSTRY)->TotalOutput(); }
999
GetResourcePool(ResourceType resource_type) const1000 const std::shared_ptr<ResourcePool> Empire::GetResourcePool(ResourceType resource_type) const {
1001 auto it = m_resource_pools.find(resource_type);
1002 if (it == m_resource_pools.end())
1003 return nullptr;
1004 return it->second;
1005 }
1006
ResourceStockpile(ResourceType type) const1007 float Empire::ResourceStockpile(ResourceType type) const {
1008 auto it = m_resource_pools.find(type);
1009 if (it == m_resource_pools.end())
1010 throw std::invalid_argument("Empire::ResourceStockpile passed invalid ResourceType");
1011 return it->second->Stockpile();
1012 }
1013
ResourceOutput(ResourceType type) const1014 float Empire::ResourceOutput(ResourceType type) const {
1015 auto it = m_resource_pools.find(type);
1016 if (it == m_resource_pools.end())
1017 throw std::invalid_argument("Empire::ResourceOutput passed invalid ResourceType");
1018 return it->second->TotalOutput();
1019 }
1020
ResourceAvailable(ResourceType type) const1021 float Empire::ResourceAvailable(ResourceType type) const {
1022 auto it = m_resource_pools.find(type);
1023 if (it == m_resource_pools.end())
1024 throw std::invalid_argument("Empire::ResourceAvailable passed invalid ResourceType");
1025 return it->second->TotalAvailable();
1026 }
1027
GetPopulationPool() const1028 const PopulationPool& Empire::GetPopulationPool() const
1029 { return m_population_pool; }
1030
Population() const1031 float Empire::Population() const
1032 { return m_population_pool.Population(); }
1033
SetResourceStockpile(ResourceType resource_type,float stockpile)1034 void Empire::SetResourceStockpile(ResourceType resource_type, float stockpile) {
1035 auto it = m_resource_pools.find(resource_type);
1036 if (it == m_resource_pools.end())
1037 throw std::invalid_argument("Empire::SetResourceStockpile passed invalid ResourceType");
1038 return it->second->SetStockpile(stockpile);
1039 }
1040
PlaceTechInQueue(const std::string & name,int pos)1041 void Empire::PlaceTechInQueue(const std::string& name, int pos/* = -1*/) {
1042 // do not add tech that is already researched
1043 if (name.empty() || TechResearched(name) || m_techs.count(name) || m_newly_researched_techs.count(name))
1044 return;
1045 const Tech* tech = GetTech(name);
1046 if (!tech || !tech->Researchable())
1047 return;
1048
1049 auto it = m_research_queue.find(name);
1050
1051 if (pos < 0 || static_cast<int>(m_research_queue.size()) <= pos) {
1052 // default to putting at end
1053 bool paused = false;
1054 if (it != m_research_queue.end()) {
1055 paused = it->paused;
1056 m_research_queue.erase(it);
1057 }
1058 m_research_queue.push_back(name, paused);
1059 } else {
1060 // put at requested position
1061 if (it < m_research_queue.begin() + pos)
1062 --pos;
1063 bool paused = false;
1064 if (it != m_research_queue.end()) {
1065 paused = it->paused;
1066 m_research_queue.erase(it);
1067 }
1068 m_research_queue.insert(m_research_queue.begin() + pos, name, paused);
1069 }
1070 }
1071
RemoveTechFromQueue(const std::string & name)1072 void Empire::RemoveTechFromQueue(const std::string& name) {
1073 auto it = m_research_queue.find(name);
1074 if (it != m_research_queue.end())
1075 m_research_queue.erase(it);
1076 }
1077
PauseResearch(const std::string & name)1078 void Empire::PauseResearch(const std::string& name) {
1079 auto it = m_research_queue.find(name);
1080 if (it != m_research_queue.end())
1081 it->paused = true;
1082 }
1083
ResumeResearch(const std::string & name)1084 void Empire::ResumeResearch(const std::string& name){
1085 auto it = m_research_queue.find(name);
1086 if (it != m_research_queue.end())
1087 it->paused = false;
1088 }
1089
SetTechResearchProgress(const std::string & name,float progress)1090 void Empire::SetTechResearchProgress(const std::string& name, float progress) {
1091 const Tech* tech = GetTech(name);
1092 if (!tech) {
1093 ErrorLogger() << "Empire::SetTechResearchProgress no such tech as: " << name;
1094 return;
1095 }
1096 if (TechResearched(name))
1097 return; // can't affect already-researched tech
1098
1099 // set progress
1100 float clamped_progress = std::min(1.0f, std::max(0.0f, progress));
1101 m_research_progress[name] = clamped_progress;
1102
1103 // if tech is complete, ensure it is on the queue, so it will be researched next turn
1104 if (clamped_progress >= tech->ResearchCost(m_id) &&
1105 !m_research_queue.InQueue(name))
1106 m_research_queue.push_back(name);
1107
1108 // don't just give tech to empire, as another effect might reduce its progress before end of turn
1109 }
1110
1111 const unsigned int MAX_PROD_QUEUE_SIZE = 500;
1112
PlaceProductionOnQueue(const ProductionQueue::ProductionItem & item,boost::uuids::uuid uuid,int number,int blocksize,int location,int pos)1113 void Empire::PlaceProductionOnQueue(const ProductionQueue::ProductionItem& item,
1114 boost::uuids::uuid uuid, int number,
1115 int blocksize, int location, int pos/* = -1*/)
1116 {
1117 if (m_production_queue.size() >= MAX_PROD_QUEUE_SIZE) {
1118 ErrorLogger() << "Empire::PlaceProductionOnQueue() : Maximum queue size reached. Aborting enqueue";
1119 return;
1120 }
1121
1122 if (item.build_type == BT_BUILDING) {
1123 // only buildings have a distinction between enqueuable and producible...
1124 if (!EnqueuableItem(BT_BUILDING, item.name, location)) {
1125 ErrorLogger() << "Empire::PlaceProductionOnQueue() : Attempted to place non-enqueuable item in queue: build_type: Building"
1126 << " name: " << item.name << " location: " << location;
1127 return;
1128 }
1129 if (!ProducibleItem(BT_BUILDING, item.name, location)) {
1130 ErrorLogger() << "Empire::PlaceProductionOnQueue() : Placed a non-buildable item in queue: build_type: Building"
1131 << " name: " << item.name << " location: " << location;
1132 return;
1133 }
1134
1135 } else if (item.build_type == BT_SHIP) {
1136 if (!ProducibleItem(BT_SHIP, item.design_id, location)) {
1137 ErrorLogger() << "Empire::PlaceProductionOnQueue() : Placed a non-buildable item in queue: build_type: Ship"
1138 << " design_id: " << item.design_id << " location: " << location;
1139 return;
1140 }
1141
1142 } else if (item.build_type == BT_STOCKPILE) {
1143 if (!ProducibleItem(BT_STOCKPILE, location)) {
1144 ErrorLogger() << "Empire::PlaceProductionOnQueue() : Placed a non-buildable item in queue: build_type: Stockpile"
1145 << " location: " << location;
1146 return;
1147 }
1148
1149 } else {
1150 throw std::invalid_argument("Empire::PlaceProductionOnQueue was passed a ProductionQueue::ProductionItem with an invalid BuildType");
1151 }
1152
1153 ProductionQueue::Element elem{item, m_id, uuid, number, number, blocksize,
1154 location, false, item.build_type != BT_STOCKPILE};
1155 if (pos < 0 || static_cast<int>(m_production_queue.size()) <= pos)
1156 m_production_queue.push_back(elem);
1157 else
1158 m_production_queue.insert(m_production_queue.begin() + pos, elem);
1159 }
1160
SetProductionQuantityAndBlocksize(int index,int quantity,int blocksize)1161 void Empire::SetProductionQuantityAndBlocksize(int index, int quantity, int blocksize) {
1162 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index)
1163 throw std::runtime_error("Empire::SetProductionQuantity() : Attempted to adjust the quantity of items to be built in a nonexistent production queue item.");
1164 DebugLogger() << "Empire::SetProductionQuantityAndBlocksize() called for item "<< m_production_queue[index].item.name << "with new quant " << quantity << " and new blocksize " << blocksize;
1165 if (quantity < 1)
1166 throw std::runtime_error("Empire::SetProductionQuantity() : Attempted to set the quantity of a build run to a value less than zero.");
1167 if (m_production_queue[index].item.build_type == BT_BUILDING && ((1 < quantity) || ( 1 < blocksize) ))
1168 throw std::runtime_error("Empire::SetProductionQuantity() : Attempted to build more than one instance of a building in the same build run.");
1169 int original_quantity = m_production_queue[index].remaining;
1170 //int original_blocksize = m_production_queue[index].blocksize;
1171 blocksize = std::max(1, blocksize);
1172 m_production_queue[index].remaining = quantity;
1173 m_production_queue[index].ordered += quantity - original_quantity;
1174 m_production_queue[index].blocksize = blocksize;
1175 //std::cout << "original block size: " << original_blocksize << " new blocksize: " << blocksize << " memory blocksize: " << m_production_queue[index].blocksize_memory << std::endl;
1176 if (blocksize <= m_production_queue[index].blocksize_memory) {
1177 // if reducing block size, progress on retained portion is unchanged.
1178 // if increasing block size, progress is proportionally reduced, unless undoing a recent reduction in block size
1179 m_production_queue[index].progress = m_production_queue[index].progress_memory;
1180 } else {
1181 m_production_queue[index].progress = m_production_queue[index].progress_memory * m_production_queue[index].blocksize_memory / blocksize;
1182 }
1183 }
1184
SplitIncompleteProductionItem(int index,boost::uuids::uuid uuid)1185 void Empire::SplitIncompleteProductionItem(int index, boost::uuids::uuid uuid) {
1186 DebugLogger() << "Empire::SplitIncompleteProductionItem() called for index " << index;
1187 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index)
1188 throw std::runtime_error("Empire::SplitIncompleteProductionItem() : Attempted to adjust the quantity of items to be built in a nonexistent production queue item.");
1189 if (m_production_queue[index].item.build_type == BT_BUILDING)
1190 throw std::runtime_error("Empire::SplitIncompleteProductionItem() : Attempted to split a production item that is not a ship.");
1191
1192 ProductionQueue::Element& elem = m_production_queue[index];
1193
1194 // if "splitting" an item with just 1 remaining, do nothing
1195 if (elem.remaining <= 1)
1196 return;
1197
1198 // add duplicate
1199 int new_item_quantity = elem.remaining - 1;
1200 elem.remaining = 1; // reduce remaining on specified to 1
1201 PlaceProductionOnQueue(elem.item, uuid, new_item_quantity, elem.blocksize, elem.location, index + 1);
1202 }
1203
DuplicateProductionItem(int index,boost::uuids::uuid uuid)1204 void Empire::DuplicateProductionItem(int index, boost::uuids::uuid uuid) {
1205 DebugLogger() << "Empire::DuplicateProductionItem() called for index " << index << " with new UUID: " << boost::uuids::to_string(uuid);
1206 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index)
1207 throw std::runtime_error("Empire::DuplicateProductionItem() : Attempted to adjust the quantity of items to be built in a nonexistent production queue item.");
1208
1209 auto& elem = m_production_queue[index];
1210 PlaceProductionOnQueue(elem.item, uuid, elem.remaining, elem.blocksize, elem.location, index + 1);
1211 }
1212
SetProductionRallyPoint(int index,int rally_point_id)1213 void Empire::SetProductionRallyPoint(int index, int rally_point_id) {
1214 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index)
1215 throw std::runtime_error("Empire::SetProductionQuantity() : Attempted to adjust the quantity of items to be built in a nonexistent production queue item.");
1216 m_production_queue[index].rally_point_id = rally_point_id;
1217 }
1218
SetProductionQuantity(int index,int quantity)1219 void Empire::SetProductionQuantity(int index, int quantity) {
1220 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index)
1221 throw std::runtime_error("Empire::SetProductionQuantity() : Attempted to adjust the quantity of items to be built in a nonexistent production queue item.");
1222 if (quantity < 1)
1223 throw std::runtime_error("Empire::SetProductionQuantity() : Attempted to set the quantity of a build run to a value less than zero.");
1224 if (m_production_queue[index].item.build_type == BT_BUILDING && 1 < quantity)
1225 throw std::runtime_error("Empire::SetProductionQuantity() : Attempted to build more than one instance of a building in the same build run.");
1226 int original_quantity = m_production_queue[index].remaining;
1227 m_production_queue[index].remaining = quantity;
1228 m_production_queue[index].ordered += quantity - original_quantity;
1229 }
1230
MoveProductionWithinQueue(int index,int new_index)1231 void Empire::MoveProductionWithinQueue(int index, int new_index) {
1232 if (index < new_index)
1233 --new_index;
1234 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index ||
1235 new_index < 0 || static_cast<int>(m_production_queue.size()) <= new_index)
1236 {
1237 DebugLogger() << "Empire::MoveProductionWithinQueue index: " << index << " new index: "
1238 << new_index << " queue size: " << m_production_queue.size();
1239 ErrorLogger() << "Attempted to move a production queue item to or from an invalid index.";
1240 return;
1241 }
1242 auto build = m_production_queue[index];
1243 m_production_queue.erase(index);
1244 m_production_queue.insert(m_production_queue.begin() + new_index, build);
1245 }
1246
RemoveProductionFromQueue(int index)1247 void Empire::RemoveProductionFromQueue(int index) {
1248 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index) {
1249 DebugLogger() << "Empire::RemoveProductionFromQueue index: " << index << " queue size: " << m_production_queue.size();
1250 ErrorLogger() << "Attempted to delete a production queue item with an invalid index.";
1251 return;
1252 }
1253 m_production_queue.erase(index);
1254 }
1255
PauseProduction(int index)1256 void Empire::PauseProduction(int index) {
1257 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index) {
1258 DebugLogger() << "Empire::PauseProduction index: " << index << " queue size: " << m_production_queue.size();
1259 ErrorLogger() << "Attempted pause a production queue item with an invalid index.";
1260 return;
1261 }
1262 m_production_queue[index].paused = true;
1263 }
1264
ResumeProduction(int index)1265 void Empire::ResumeProduction(int index) {
1266 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index) {
1267 DebugLogger() << "Empire::ResumeProduction index: " << index << " queue size: " << m_production_queue.size();
1268 ErrorLogger() << "Attempted resume a production queue item with an invalid index.";
1269 return;
1270 }
1271 m_production_queue[index].paused = false;
1272 }
1273
AllowUseImperialPP(int index,bool allow)1274 void Empire::AllowUseImperialPP(int index, bool allow /*=true*/) {
1275 if (index < 0 || static_cast<int>(m_production_queue.size()) <= index) {
1276 DebugLogger() << "Empire::AllowUseImperialPP index: " << index << " queue size: " << m_production_queue.size();
1277 ErrorLogger() << "Attempted allow/disallow use of the imperial PP stockpile for a production queue item with an invalid index.";
1278 return;
1279 }
1280 DebugLogger() << "Empire::AllowUseImperialPP allow: " << allow << " index: " << index << " queue size: " << m_production_queue.size();
1281 m_production_queue[index].allowed_imperial_stockpile_use = allow;
1282 }
1283
ConquerProductionQueueItemsAtLocation(int location_id,int empire_id)1284 void Empire::ConquerProductionQueueItemsAtLocation(int location_id, int empire_id) {
1285 if (location_id == INVALID_OBJECT_ID) {
1286 ErrorLogger() << "Empire::ConquerProductionQueueItemsAtLocation: tried to conquer build items located at an invalid location";
1287 return;
1288 }
1289
1290 DebugLogger() << "Empire::ConquerProductionQueueItemsAtLocation: conquering items located at "
1291 << location_id << " to empire " << empire_id;
1292
1293 Empire* to_empire = GetEmpire(empire_id); // may be null
1294 if (!to_empire && empire_id != ALL_EMPIRES) {
1295 ErrorLogger() << "Couldn't get empire with id " << empire_id;
1296 return;
1297 }
1298
1299
1300 for (auto& entry : Empires()) {
1301 int from_empire_id = entry.first;
1302 if (from_empire_id == empire_id) continue; // skip this empire; can't capture one's own ProductionItems
1303
1304 Empire* from_empire = entry.second;
1305 ProductionQueue& queue = from_empire->m_production_queue;
1306
1307 for (auto queue_it = queue.begin(); queue_it != queue.end(); ) {
1308 auto elem = *queue_it;
1309 if (elem.location != location_id) {
1310 ++queue_it;
1311 continue; // skip projects with wrong location
1312 }
1313
1314 ProductionQueue::ProductionItem item = elem.item;
1315
1316 if (item.build_type == BT_BUILDING) {
1317 std::string name = item.name;
1318 const BuildingType* type = GetBuildingType(name);
1319 if (!type) {
1320 ErrorLogger() << "ConquerProductionQueueItemsAtLocation couldn't get building with name " << name;
1321 continue;
1322 }
1323
1324 CaptureResult result = type->GetCaptureResult(from_empire_id, empire_id, location_id, true);
1325
1326 if (result == CR_DESTROY) {
1327 // item removed from current queue, NOT added to conquerer's queue
1328 queue_it = queue.erase(queue_it);
1329
1330 } else if (result == CR_CAPTURE) {
1331 if (to_empire) {
1332 // item removed from current queue, added to conquerer's queue
1333 ProductionQueue::Element new_elem(item, empire_id, elem.uuid, elem.ordered,
1334 elem.remaining, 1, location_id);
1335 new_elem.progress = elem.progress;
1336 to_empire->m_production_queue.push_back(new_elem);
1337
1338 queue_it = queue.erase(queue_it);
1339 } else {
1340 // else do nothing; no empire can't capure things
1341 ++queue_it;
1342 }
1343
1344 } else if (result == INVALID_CAPTURE_RESULT) {
1345 ErrorLogger() << "Empire::ConquerBuildsAtLocationFromEmpire: BuildingType had an invalid CaptureResult";
1346 } else {
1347 ++queue_it;
1348 }
1349 // otherwise do nothing: item left on current queue, conquerer gets nothing
1350 } else {
1351 ++queue_it;
1352 }
1353
1354 // TODO: other types of build item...
1355 }
1356 }
1357 }
1358
AddNewlyResearchedTechToGrantAtStartOfNextTurn(const std::string & name)1359 void Empire::AddNewlyResearchedTechToGrantAtStartOfNextTurn(const std::string& name) {
1360 const Tech* tech = GetTech(name);
1361 if (!tech) {
1362 ErrorLogger() << "Empire::AddNewlyResearchedTechToGrantAtStartOfNextTurn given an invalid tech: " << name;
1363 return;
1364 }
1365
1366 if (m_techs.count(name))
1367 return;
1368
1369 // Mark given tech to be granted at next turn. If it was already marked, skip writing a SitRep message
1370 m_newly_researched_techs.insert(name);
1371 }
1372
ApplyNewTechs()1373 void Empire::ApplyNewTechs() {
1374 for (auto new_tech : m_newly_researched_techs) {
1375 const Tech* tech = GetTech(new_tech);
1376 if (!tech) {
1377 ErrorLogger() << "Empire::ApplyNewTech has an invalid entry in m_newly_researched_techs: " << new_tech;
1378 continue;
1379 }
1380
1381 for (const UnlockableItem& item : tech->UnlockedItems())
1382 UnlockItem(item); // potential infinite if a tech (in)directly unlocks itself?
1383
1384 if (!m_techs.count(new_tech)) {
1385 m_techs[new_tech] = CurrentTurn();
1386 AddSitRepEntry(CreateTechResearchedSitRep(new_tech));
1387 }
1388 }
1389 m_newly_researched_techs.clear();
1390 }
1391
UnlockItem(const UnlockableItem & item)1392 void Empire::UnlockItem(const UnlockableItem& item) {
1393 switch (item.type) {
1394 case UIT_BUILDING:
1395 AddBuildingType(item.name);
1396 break;
1397 case UIT_SHIP_PART:
1398 AddShipPart(item.name);
1399 break;
1400 case UIT_SHIP_HULL:
1401 AddShipHull(item.name);
1402 break;
1403 case UIT_SHIP_DESIGN:
1404 AddShipDesign(GetPredefinedShipDesignManager().GetDesignID(item.name));
1405 break;
1406 case UIT_TECH:
1407 AddNewlyResearchedTechToGrantAtStartOfNextTurn(item.name);
1408 break;
1409 default:
1410 ErrorLogger() << "Empire::UnlockItem : passed UnlockableItem with unrecognized UnlockableItemType";
1411 }
1412 }
1413
AddBuildingType(const std::string & name)1414 void Empire::AddBuildingType(const std::string& name) {
1415 const BuildingType* building_type = GetBuildingType(name);
1416 if (!building_type) {
1417 ErrorLogger() << "Empire::AddBuildingType given an invalid building type name: " << name;
1418 return;
1419 }
1420 if (!building_type->Producible())
1421 return;
1422 if (m_available_building_types.count(name))
1423 return;
1424 m_available_building_types.insert(name);
1425 AddSitRepEntry(CreateBuildingTypeUnlockedSitRep(name));
1426 }
1427
AddShipPart(const std::string & name)1428 void Empire::AddShipPart(const std::string& name) {
1429 const ShipPart* ship_part = GetShipPart(name);
1430 if (!ship_part) {
1431 ErrorLogger() << "Empire::AddShipPart given an invalid ship part name: " << name;
1432 return;
1433 }
1434 if (!ship_part->Producible())
1435 return;
1436 m_available_ship_parts.insert(name);
1437 AddSitRepEntry(CreateShipPartUnlockedSitRep(name));
1438 }
1439
AddShipHull(const std::string & name)1440 void Empire::AddShipHull(const std::string& name) {
1441 const ShipHull* ship_hull = GetShipHull(name);
1442 if (!ship_hull) {
1443 ErrorLogger() << "Empire::AddShipHull given an invalid hull type name: " << name;
1444 return;
1445 }
1446 if (!ship_hull->Producible())
1447 return;
1448 m_available_ship_hulls.insert(name);
1449 AddSitRepEntry(CreateShipHullUnlockedSitRep(name));
1450 }
1451
AddExploredSystem(int ID)1452 void Empire::AddExploredSystem(int ID) {
1453 if (Objects().get<System>(ID))
1454 m_explored_systems.insert(ID);
1455 else
1456 ErrorLogger() << "Empire::AddExploredSystem given an invalid system id: " << ID;
1457 }
1458
NewShipName()1459 std::string Empire::NewShipName() {
1460 static std::vector<std::string> ship_names = UserStringList("SHIP_NAMES");
1461 if (ship_names.empty())
1462 ship_names.push_back(UserString("OBJ_SHIP"));
1463
1464 // select name randomly from list
1465 int ship_name_idx = RandSmallInt(0, static_cast<int>(ship_names.size()) - 1);
1466 std::string retval = ship_names[ship_name_idx];
1467 int times_name_used = ++m_ship_names_used[retval];
1468 if (1 < times_name_used)
1469 retval += " " + RomanNumber(times_name_used);
1470 return retval;
1471 }
1472
AddShipDesign(int ship_design_id,int next_design_id)1473 void Empire::AddShipDesign(int ship_design_id, int next_design_id) {
1474 /* Check if design id is valid. That is, check that it corresponds to an
1475 * existing shipdesign in the universe. On clients, this means that this
1476 * empire knows about this ship design and the server consequently sent the
1477 * design to this player. On the server, all existing ship designs will be
1478 * valid, so this just adds this design's id to those that this empire will
1479 * retain as one of it's ship designs, which are those displayed in the GUI
1480 * list of available designs for human players, and */
1481 if (ship_design_id == next_design_id)
1482 return;
1483
1484 const ShipDesign* ship_design = GetUniverse().GetShipDesign(ship_design_id);
1485 if (ship_design) { // don't check if design is producible; adding a ship design is useful for more than just producing it
1486 // design is valid, so just add the id to empire's set of ids that it knows about
1487 if (!m_known_ship_designs.count(ship_design_id)) {
1488 m_known_ship_designs.insert(ship_design_id);
1489
1490 ShipDesignsChangedSignal();
1491
1492 TraceLogger() << "AddShipDesign:: " << ship_design->Name() << " (" << ship_design_id
1493 << ") to empire #" << EmpireID();
1494 }
1495 } else {
1496 // design in not valid
1497 ErrorLogger() << "Empire::AddShipDesign(int ship_design_id) was passed a design id that this empire doesn't know about, or that doesn't exist";
1498 }
1499 }
1500
AddShipDesign(ShipDesign * ship_design)1501 int Empire::AddShipDesign(ShipDesign* ship_design) {
1502 Universe& universe = GetUniverse();
1503 /* check if there already exists this same design in the universe. On clients, this checks whether this empire
1504 knows of this exact design and is trying to re-add it. On the server, this checks whether this exact design
1505 exists at all yet */
1506 for (Universe::ship_design_iterator it = universe.beginShipDesigns(); it != universe.endShipDesigns(); ++it) {
1507 if (ship_design == it->second) {
1508 // ship design is already present in universe. just need to add it to the empire's set of ship designs
1509 int ship_design_id = it->first;
1510 AddShipDesign(ship_design_id);
1511 return ship_design_id;
1512 }
1513 }
1514
1515 bool success = universe.InsertShipDesign(ship_design);
1516
1517 if (!success) {
1518 ErrorLogger() << "Empire::AddShipDesign Unable to add new design to universe";
1519 return INVALID_OBJECT_ID;
1520 }
1521
1522 auto new_design_id = ship_design->ID();
1523 AddShipDesign(new_design_id);
1524
1525 return new_design_id;
1526 }
1527
RemoveShipDesign(int ship_design_id)1528 void Empire::RemoveShipDesign(int ship_design_id) {
1529 if (m_known_ship_designs.count(ship_design_id)) {
1530 m_known_ship_designs.erase(ship_design_id);
1531 ShipDesignsChangedSignal();
1532 } else {
1533 DebugLogger() << "Empire::RemoveShipDesign: this empire did not have design with id " << ship_design_id;
1534 }
1535 }
1536
AddSitRepEntry(const SitRepEntry & entry)1537 void Empire::AddSitRepEntry(const SitRepEntry& entry)
1538 { m_sitrep_entries.push_back(entry); }
1539
RemoveTech(const std::string & name)1540 void Empire::RemoveTech(const std::string& name)
1541 { m_techs.erase(name); }
1542
LockItem(const UnlockableItem & item)1543 void Empire::LockItem(const UnlockableItem& item) {
1544 switch (item.type) {
1545 case UIT_BUILDING:
1546 RemoveBuildingType(item.name);
1547 break;
1548 case UIT_SHIP_PART:
1549 RemoveShipPart(item.name);
1550 break;
1551 case UIT_SHIP_HULL:
1552 RemoveShipHull(item.name);
1553 break;
1554 case UIT_SHIP_DESIGN:
1555 RemoveShipDesign(GetPredefinedShipDesignManager().GetDesignID(item.name));
1556 break;
1557 case UIT_TECH:
1558 RemoveTech(item.name);
1559 break;
1560 default:
1561 ErrorLogger() << "Empire::LockItem : passed UnlockableItem with unrecognized UnlockableItemType";
1562 }
1563 }
1564
RemoveBuildingType(const std::string & name)1565 void Empire::RemoveBuildingType(const std::string& name) {
1566 if (!m_available_building_types.count(name))
1567 DebugLogger() << "Empire::RemoveBuildingType asked to remove building type " << name << " that was no available to this empire";
1568 m_available_building_types.erase(name);
1569 }
1570
RemoveShipPart(const std::string & name)1571 void Empire::RemoveShipPart(const std::string& name) {
1572 auto it = m_available_ship_parts.find(name);
1573 if (it == m_available_ship_parts.end())
1574 DebugLogger() << "Empire::RemoveShipPart asked to remove part type " << name << " that was no available to this empire";
1575 m_available_ship_parts.erase(name);
1576 }
1577
RemoveShipHull(const std::string & name)1578 void Empire::RemoveShipHull(const std::string& name) {
1579 auto it = m_available_ship_hulls.find(name);
1580 if (it == m_available_ship_hulls.end())
1581 DebugLogger() << "Empire::RemoveShipHull asked to remove hull type " << name << " that was no available to this empire";
1582 m_available_ship_hulls.erase(name);
1583 }
1584
ClearSitRep()1585 void Empire::ClearSitRep()
1586 { m_sitrep_entries.clear(); }
1587
1588 namespace {
1589 // remove nonexistant / invalid techs from queue
SanitizeResearchQueue(ResearchQueue & queue)1590 void SanitizeResearchQueue(ResearchQueue& queue) {
1591 bool done = false;
1592 while (!done) {
1593 auto it = queue.begin();
1594 while (true) {
1595 if (it == queue.end()) {
1596 done = true; // got all the way through the queue without finding an invalid tech
1597 break;
1598 } else if (!GetTech(it->name)) {
1599 DebugLogger() << "SanitizeResearchQueue for empire " << queue.EmpireID() << " removed invalid tech: " << it->name;
1600 queue.erase(it); // remove invalid tech, end inner loop without marking as finished
1601 break;
1602 } else {
1603 ++it; // check next element
1604 }
1605 }
1606 }
1607 }
1608 }
1609
CheckResearchProgress()1610 std::vector<std::string> Empire::CheckResearchProgress() {
1611 SanitizeResearchQueue(m_research_queue);
1612
1613 float spent_rp{0.0f};
1614 float total_rp_available = m_resource_pools[RE_RESEARCH]->TotalAvailable();
1615
1616 // process items on queue
1617 std::vector<std::string> to_erase_from_queue_and_grant_next_turn;
1618 for (auto& elem : m_research_queue) {
1619 const Tech* tech = GetTech(elem.name);
1620 if (!tech) {
1621 ErrorLogger() << "Empire::CheckResearchProgress couldn't find tech on queue, even after sanitizing!";
1622 continue;
1623 }
1624 float& progress = m_research_progress[elem.name];
1625 float tech_cost = tech->ResearchCost(m_id);
1626 progress += elem.allocated_rp / std::max(EPSILON, tech_cost);
1627 spent_rp += elem.allocated_rp;
1628 if (tech->ResearchCost(m_id) - EPSILON <= progress * tech_cost) {
1629 m_research_progress.erase(elem.name);
1630 to_erase_from_queue_and_grant_next_turn.push_back(elem.name);
1631 }
1632 }
1633
1634 //DebugLogger() << m_research_queue.Dump();
1635 float rp_left_to_spend = total_rp_available - spent_rp;
1636 //DebugLogger() << "leftover RP: " << rp_left_to_spend;
1637 // auto-allocate any excess RP left over after player-specified queued techs
1638
1639 // if there are left over RPs, any tech on the queue presumably can't
1640 // have RP allocated to it
1641 std::unordered_set<std::string> techs_not_suitable_for_auto_allocation;
1642 for (auto& elem : m_research_queue)
1643 techs_not_suitable_for_auto_allocation.insert(elem.name);
1644
1645 // for all available and suitable techs, store ordered by cost to complete
1646 std::multimap<double, std::string> costs_to_complete_available_unpaused_techs;
1647 for (const auto& tech : GetTechManager()) {
1648 const std::string& tech_name = tech->Name();
1649 if (techs_not_suitable_for_auto_allocation.count(tech_name) > 0)
1650 continue;
1651 if (this->GetTechStatus(tech_name) != TS_RESEARCHABLE)
1652 continue;
1653 if (!tech->Researchable())
1654 continue;
1655 double progress = this->ResearchProgress(tech_name);
1656 double total_cost = tech->ResearchCost(m_id);
1657 if (progress >= total_cost)
1658 continue;
1659 costs_to_complete_available_unpaused_techs.emplace(total_cost - progress, tech_name);
1660 }
1661
1662 // in order of minimum additional cost to complete, allocate RP to
1663 // techs up to available RP and per-turn limits
1664 for (auto const& cost_tech : costs_to_complete_available_unpaused_techs) {
1665 if (rp_left_to_spend <= EPSILON)
1666 break;
1667
1668 const Tech* tech = GetTech(cost_tech.second);
1669 if (!tech)
1670 continue;
1671
1672 //DebugLogger() << "extra tech: " << cost_tech.second << " needs: " << cost_tech.first << " more RP to finish";
1673
1674 float RPs_per_turn_limit = tech->PerTurnCost(m_id);
1675 float tech_total_cost = tech->ResearchCost(m_id);
1676 float progress_fraction = m_research_progress[cost_tech.second];
1677
1678 float progress_fraction_left = 1.0f - progress_fraction;
1679 float max_progress_per_turn = RPs_per_turn_limit / tech_total_cost;
1680 float progress_possible_with_available_rp = rp_left_to_spend / tech_total_cost;
1681
1682 //DebugLogger() << "... progress left: " << progress_fraction_left
1683 // << " max per turn: " << max_progress_per_turn
1684 // << " progress possible with available rp: " << progress_possible_with_available_rp;
1685
1686 float progress_increase = std::min(
1687 progress_fraction_left,
1688 std::min(max_progress_per_turn, progress_possible_with_available_rp));
1689
1690 float consumed_rp = progress_increase * tech_total_cost;
1691
1692 m_research_progress[cost_tech.second] += progress_increase;
1693 rp_left_to_spend -= consumed_rp;
1694
1695 if (tech->ResearchCost(m_id) - EPSILON <= m_research_progress[cost_tech.second] * tech_total_cost)
1696 to_erase_from_queue_and_grant_next_turn.push_back(cost_tech.second);
1697
1698 //DebugLogger() << "... allocated: " << consumed_rp << " to increase progress by: " << progress_increase;
1699 }
1700
1701 // remove completed items from queue (after consuming extra RP, as that
1702 // determination uses the contents of the queue as input)
1703 for (const std::string& tech_name : to_erase_from_queue_and_grant_next_turn) {
1704 auto temp_it = m_research_queue.find(tech_name);
1705 if (temp_it != m_research_queue.end())
1706 m_research_queue.erase(temp_it);
1707 }
1708
1709 // can uncomment following line when / if research stockpiling is enabled...
1710 // m_resource_pools[RE_RESEARCH]->SetStockpile(m_resource_pools[RE_RESEARCH]->TotalAvailable() - m_research_queue.TotalRPsSpent());
1711 return to_erase_from_queue_and_grant_next_turn;
1712 }
1713
CheckProductionProgress()1714 void Empire::CheckProductionProgress() {
1715 DebugLogger() << "========Empire::CheckProductionProgress=======";
1716 // following commented line should be redundant, as previous call to
1717 // UpdateResourcePools should have generated necessary info
1718 // m_production_queue.Update();
1719
1720 Universe& universe = GetUniverse();
1721
1722 std::map<int, std::vector<std::shared_ptr<Ship>>> system_new_ships;
1723 std::map<int, int> new_ship_rally_point_ids;
1724
1725 // preprocess the queue to get all the costs and times of all items
1726 // at every location at which they are being produced,
1727 // before doing any generation of new objects or other modifications
1728 // of the gamestate. this will ensure that the cost of items doesn't
1729 // change while the queue is being processed, so that if there is
1730 // sufficent PP to complete an object at the start of a turn,
1731 // items above it on the queue getting finished don't increase the
1732 // cost and result in it not being finished that turn.
1733 std::map<std::pair<ProductionQueue::ProductionItem, int>, std::pair<float, int>>
1734 queue_item_costs_and_times;
1735 for (auto& elem : m_production_queue) {
1736 // for items that don't depend on location, only store cost/time once
1737 int location_id = (elem.item.CostIsProductionLocationInvariant() ? INVALID_OBJECT_ID : elem.location);
1738 auto key = std::make_pair(elem.item, location_id);
1739
1740 if (!queue_item_costs_and_times.count(key))
1741 queue_item_costs_and_times[key] = ProductionCostAndTime(elem);
1742 }
1743
1744 //for (auto& entry : queue_item_costs_and_times)
1745 //{ DebugLogger() << entry.first.first.design_id << " : " << entry.second.first; }
1746
1747
1748 // go through queue, updating production progress. If a production item is
1749 // completed, create the produced object or take whatever other action is
1750 // appropriate, and record that queue item as complete, so it can be erased
1751 // from the queue
1752 std::vector<int> to_erase;
1753 for (unsigned int i = 0; i < m_production_queue.size(); ++i) {
1754 auto& elem = m_production_queue[i];
1755 float item_cost;
1756 int build_turns;
1757
1758 // for items that don't depend on location, only store cost/time once
1759 int location_id = (elem.item.CostIsProductionLocationInvariant() ? INVALID_OBJECT_ID : elem.location);
1760 std::pair<ProductionQueue::ProductionItem, int> key(elem.item, location_id);
1761
1762 std::tie(item_cost, build_turns) = queue_item_costs_and_times[key];
1763 if (item_cost < 0.01f || build_turns < 1) {
1764 ErrorLogger() << "Empire::CheckProductionProgress got strang cost/time: " << item_cost << " / " << build_turns;
1765 break;
1766 }
1767
1768 item_cost *= elem.blocksize;
1769
1770 DebugLogger() << "elem: " << elem.Dump();
1771 DebugLogger() << " allocated: " << elem.allocated_pp;
1772 DebugLogger() << " initial progress: " << elem.progress;
1773
1774 elem.progress += elem.allocated_pp / std::max(EPSILON, item_cost); // add progress for allocated PP to queue item
1775 elem.progress_memory = elem.progress;
1776 elem.blocksize_memory = elem.blocksize;
1777
1778 DebugLogger() << " updated progress: " << elem.progress;
1779 DebugLogger() << " ";
1780
1781 std::string build_description;
1782 switch (elem.item.build_type) {
1783 case BT_BUILDING: {
1784 build_description = "BuildingType " + elem.item.name;
1785 break;
1786 }
1787 case BT_SHIP: {
1788 build_description = "Ships(s) with design id " + std::to_string(elem.item.design_id);
1789 break;
1790 }
1791 case BT_STOCKPILE: {
1792 build_description = "Stockpile PP transfer";
1793 break;
1794 }
1795 default:
1796 build_description = "unknown build type";
1797 }
1798
1799 auto build_location = Objects().get(elem.location);
1800 if (!build_location || (elem.item.build_type == BT_BUILDING && build_location->ObjectType() != OBJ_PLANET)) {
1801 ErrorLogger() << "Couldn't get valid build location for completed " << build_description;
1802 continue;
1803 }
1804 auto system = Objects().get<System>(build_location->SystemID());
1805 // TODO: account for shipyards and/or other ship production
1806 // sites that are in interstellar space, if needed
1807 if (!system) {
1808 ErrorLogger() << "Empire::CheckProductionProgress couldn't get system for producing new " << build_description;
1809 continue;
1810 }
1811
1812 // check location condition before each item is created, so
1813 // that items being produced can prevent subsequent
1814 // completions on the same turn from going through
1815 if (!this->ProducibleItem(elem.item, elem.location)) {
1816 DebugLogger() << "Location test failed for " << build_description << " at location " << build_location->Name();
1817 continue;
1818 }
1819
1820
1821 // only if accumulated PP is sufficient, the item can be completed
1822 if (item_cost - EPSILON > elem.progress*item_cost)
1823 continue;
1824
1825
1826 // only if consumed resources are available, then item can be completd
1827 bool consumption_impossible = false;
1828 std::map<std::string, std::map<int, float>> sc = elem.item.CompletionSpecialConsumption(elem.location);
1829 for (auto& special_type : sc) {
1830 if (consumption_impossible)
1831 break;
1832 for (auto& special_meter : special_type.second) {
1833 auto obj = Objects().get(special_meter.first);
1834 float capacity = obj ? obj->SpecialCapacity(special_type.first) : 0.0f;
1835 if (capacity < special_meter.second * elem.blocksize) {
1836 consumption_impossible = true;
1837 break;
1838 }
1839 }
1840 }
1841 auto mc = elem.item.CompletionMeterConsumption(elem.location);
1842 for (auto& meter_type : mc) {
1843 if (consumption_impossible)
1844 break;
1845 for (auto& object_meter : meter_type.second) {
1846 auto obj = Objects().get(object_meter.first);
1847 const Meter* meter = obj ? obj->GetMeter(meter_type.first) : nullptr;
1848 if (!meter || meter->Current() < object_meter.second * elem.blocksize) {
1849 consumption_impossible = true;
1850 break;
1851 }
1852 }
1853 }
1854 if (consumption_impossible)
1855 continue;
1856
1857
1858 // deduct progress for complete item from accumulated progress, so that next
1859 // repetition can continue accumulating PP, but don't set progress to 0, as
1860 // this way overflow progress / PP allocated this turn can be used for the
1861 // next repetition of the item.
1862 elem.progress -= 1.0f;
1863 if (elem.progress < 0.0f) {
1864 if (elem.progress < -1e-3)
1865 ErrorLogger() << "Somehow got negative progress (" << elem.progress
1866 << ") after deducting progress for completed item...";
1867 elem.progress = 0.0f;
1868 }
1869
1870 elem.progress_memory = elem.progress;
1871 DebugLogger() << "Completed an item: " << elem.item.name;
1872
1873
1874 // consume the item's special and meter consumption
1875 for (auto& special_type : sc) {
1876 for (auto& special_meter : special_type.second) {
1877 auto obj = Objects().get(special_meter.first);
1878 if (!obj)
1879 continue;
1880 if (!obj->HasSpecial(special_type.first))
1881 continue;
1882 float cur_capacity = obj->SpecialCapacity(special_type.first);
1883 float new_capacity = std::max(0.0f, cur_capacity - special_meter.second * elem.blocksize);
1884 obj->SetSpecialCapacity(special_type.first, new_capacity);
1885 }
1886 }
1887 for (auto& meter_type : mc) {
1888 for (const auto& object_meter : meter_type.second) {
1889 auto obj = Objects().get(object_meter.first);
1890 if (!obj)
1891 continue;
1892 Meter*meter = obj->GetMeter(meter_type.first);
1893 if (!meter)
1894 continue;
1895 float cur_meter = meter->Current();
1896 float new_meter = cur_meter - object_meter.second * elem.blocksize;
1897 meter->SetCurrent(new_meter);
1898 meter->BackPropagate();
1899 }
1900 }
1901
1902
1903 // create actual thing(s) being produced
1904 switch (elem.item.build_type) {
1905 case BT_BUILDING: {
1906 auto planet = Objects().get<Planet>(elem.location);
1907
1908 // create new building
1909 auto building = universe.InsertNew<Building>(m_id, elem.item.name, m_id);
1910 planet->AddBuilding(building->ID());
1911 building->SetPlanetID(planet->ID());
1912 system->Insert(building);
1913
1914 // record building production in empire stats
1915 if (m_building_types_produced.count(elem.item.name))
1916 m_building_types_produced[elem.item.name]++;
1917 else
1918 m_building_types_produced[elem.item.name] = 1;
1919
1920 AddSitRepEntry(CreateBuildingBuiltSitRep(building->ID(), planet->ID()));
1921 DebugLogger() << "New Building created on turn: " << CurrentTurn();
1922 break;
1923 }
1924
1925 case BT_SHIP: {
1926 if (elem.blocksize < 1)
1927 break; // nothing to do!
1928
1929 // get species for this ship. use popcenter species if build
1930 // location is a popcenter, or use ship species if build
1931 // location is a ship, or use empire capital species if there
1932 // is a valid capital, or otherwise ???
1933 // TODO: Add more fallbacks if necessary
1934 std::string species_name;
1935 if (auto location_pop_center = std::dynamic_pointer_cast<const PopCenter>(build_location))
1936 species_name = location_pop_center->SpeciesName();
1937 else if (auto location_ship = std::dynamic_pointer_cast<const Ship>(build_location))
1938 species_name = location_ship->SpeciesName();
1939 else if (auto capital_planet = Objects().get<Planet>(this->CapitalID()))
1940 species_name = capital_planet->SpeciesName();
1941 // else give up...
1942 if (species_name.empty()) {
1943 // only really a problem for colony ships, which need to have a species to function
1944 const auto* design = GetShipDesign(elem.item.design_id);
1945 if (!design) {
1946 ErrorLogger() << "Couldn't get ShipDesign with id: " << elem.item.design_id;
1947 break;
1948 }
1949 if (design->CanColonize()) {
1950 ErrorLogger() << "Couldn't get species in order to make colony ship!";
1951 break;
1952 }
1953 }
1954
1955 std::shared_ptr<Ship> ship;
1956
1957 for (int count = 0; count < elem.blocksize; count++) {
1958 // create ship
1959 ship = universe.InsertNew<Ship>(m_id, elem.item.design_id, species_name, m_id);
1960 system->Insert(ship);
1961
1962 // record ship production in empire stats
1963 if (m_ship_designs_produced.count(elem.item.design_id))
1964 m_ship_designs_produced[elem.item.design_id]++;
1965 else
1966 m_ship_designs_produced[elem.item.design_id] = 1;
1967 if (m_species_ships_produced.count(species_name))
1968 m_species_ships_produced[species_name]++;
1969 else
1970 m_species_ships_produced[species_name] = 1;
1971
1972
1973 // set active meters that have associated max meters to an
1974 // initial very large value, so that when the active meters are
1975 // later clamped, they will equal the max meter after effects
1976 // have been applied, letting new ships start with maxed
1977 // everything that is traced with an associated max meter.
1978 ship->SetShipMetersToMax();
1979 // set ship speed so that it can be affected by non-zero speed checks
1980 if (auto* design = GetShipDesign(elem.item.design_id))
1981 ship->GetMeter(METER_SPEED)->Set(design->Speed(), design->Speed());
1982 ship->BackPropagateMeters();
1983
1984 ship->Rename(NewShipName());
1985
1986 // store ships to put into fleets later
1987 system_new_ships[system->ID()].push_back(ship);
1988
1989 // store ship rally points
1990 if (elem.rally_point_id != INVALID_OBJECT_ID)
1991 new_ship_rally_point_ids[ship->ID()] = elem.rally_point_id;
1992 }
1993 // add sitrep
1994 if (elem.blocksize == 1) {
1995 AddSitRepEntry(CreateShipBuiltSitRep(ship->ID(), system->ID(), ship->DesignID()));
1996 DebugLogger() << "New Ship, id " << ship->ID() << ", created on turn: " << ship->CreationTurn();
1997 } else {
1998 AddSitRepEntry(CreateShipBlockBuiltSitRep(system->ID(), ship->DesignID(), elem.blocksize));
1999 DebugLogger() << "New block of "<< elem.blocksize << " ships created on turn: " << ship->CreationTurn();
2000 }
2001 break;
2002 }
2003
2004 case BT_STOCKPILE: {
2005 DebugLogger() << "Finished a transfer to stockpile";
2006 break;
2007 }
2008
2009 default:
2010 ErrorLogger() << "Build item of unknown build type finished on production queue.";
2011 break;
2012 }
2013
2014 if (!--m_production_queue[i].remaining) { // decrement number of remaining items to be produced in current queue element
2015 to_erase.push_back(i); // remember completed element so that it can be removed from queue
2016 DebugLogger() << "Marking completed production queue item to be removed from queue";
2017 }
2018 }
2019
2020 // create fleets for new ships and put ships into fleets
2021 for (auto& entry : system_new_ships) {
2022 auto system = Objects().get<System>(entry.first);
2023 if (!system) {
2024 ErrorLogger() << "Couldn't get system with id " << entry.first << " for creating new fleets for newly produced ships";
2025 continue;
2026 }
2027
2028 auto& new_ships = entry.second;
2029 if (new_ships.empty())
2030 continue;
2031
2032 // group ships into fleets by rally point and design
2033 std::map<int, std::map<int, std::vector<std::shared_ptr<Ship>>>>
2034 new_ships_by_rally_point_id_and_design_id;
2035 for (auto& ship : new_ships) {
2036 int rally_point_id = INVALID_OBJECT_ID;
2037
2038 auto rally_it = new_ship_rally_point_ids.find(ship->ID());
2039 if (rally_it != new_ship_rally_point_ids.end())
2040 rally_point_id = rally_it->second;
2041
2042 new_ships_by_rally_point_id_and_design_id[rally_point_id][ship->DesignID()].push_back(ship);
2043 }
2044
2045 // create fleets for ships with the same rally point, grouped by
2046 // ship design
2047 // Do not group unarmed ships with no troops (i.e. scouts and
2048 // colony ships).
2049 for (auto& rally_ships : new_ships_by_rally_point_id_and_design_id) {
2050 int rally_point_id = rally_ships.first;
2051 auto& new_ships_by_design = rally_ships.second;
2052
2053 for (auto& ships_by_design : new_ships_by_design) {
2054 std::vector<int> ship_ids;
2055
2056 auto& ships = ships_by_design.second;
2057 if (ships.empty())
2058 continue;
2059
2060 // create a single fleet for combat ships and individual
2061 // fleets for non-combat ships
2062 bool individual_fleets = !((*ships.begin())->IsArmed()
2063 || (*ships.begin())->HasFighters()
2064 || (*ships.begin())->CanHaveTroops()
2065 || (*ships.begin())->CanBombard());
2066
2067 std::vector<std::shared_ptr<Fleet>> fleets;
2068 std::shared_ptr<Fleet> fleet;
2069
2070 if (!individual_fleets) {
2071 fleet = universe.InsertNew<Fleet>("", system->X(), system->Y(), m_id);
2072
2073 system->Insert(fleet);
2074 // set prev system to prevent conflicts with CalculateRouteTo used for
2075 // rally points below, but leave next system as INVALID_OBJECT_ID so
2076 // fleet won't necessarily be disqualified from making blockades if it
2077 // is left stationary
2078 fleet->SetNextAndPreviousSystems(INVALID_OBJECT_ID, system->ID());
2079 // set invalid arrival starlane so that fleet won't necessarily be free from blockades
2080 fleet->SetArrivalStarlane(INVALID_OBJECT_ID);
2081
2082 fleets.push_back(fleet);
2083 }
2084
2085 for (auto& ship : ships) {
2086 if (individual_fleets) {
2087 fleet = universe.InsertNew<Fleet>("", system->X(), system->Y(), m_id);
2088
2089 system->Insert(fleet);
2090 // set prev system to prevent conflicts with CalculateRouteTo used for
2091 // rally points below, but leave next system as INVALID_OBJECT_ID so
2092 // fleet won't necessarily be disqualified from making blockades if it
2093 // is left stationary
2094 fleet->SetNextAndPreviousSystems(INVALID_OBJECT_ID, system->ID());
2095 // set invalid arrival starlane so that fleet won't necessarily be free from blockades
2096 fleet->SetArrivalStarlane(INVALID_OBJECT_ID);
2097
2098 fleets.push_back(fleet);
2099 }
2100 ship_ids.push_back(ship->ID());
2101 fleet->AddShips({ship->ID()});
2102 ship->SetFleetID(fleet->ID());
2103 }
2104
2105 for (auto& next_fleet : fleets) {
2106 // rename fleet, given its id and the ship that is in it
2107 next_fleet->Rename(next_fleet->GenerateFleetName());
2108 next_fleet->SetAggressive(next_fleet->HasArmedShips());
2109
2110 if (rally_point_id != INVALID_OBJECT_ID) {
2111 if (Objects().get<System>(rally_point_id)) {
2112 next_fleet->CalculateRouteTo(rally_point_id);
2113 } else if (auto rally_obj = Objects().get(rally_point_id)) {
2114 if (Objects().get<System>(rally_obj->SystemID()))
2115 next_fleet->CalculateRouteTo(rally_obj->SystemID());
2116 } else {
2117 ErrorLogger() << "Unable to find system to route to with rally point id: " << rally_point_id;
2118 }
2119 }
2120
2121 DebugLogger() << "New Fleet \"" << next_fleet->Name()
2122 <<"\" created on turn: " << next_fleet->CreationTurn();
2123 }
2124 }
2125 }
2126 }
2127
2128 // removed completed items from queue
2129 for (auto it = to_erase.rbegin(); it != to_erase.rend(); ++it)
2130 m_production_queue.erase(*it);
2131
2132 // update stockpile
2133 SetResourceStockpile(RE_INDUSTRY, m_production_queue.ExpectedNewStockpileAmount());
2134 }
2135
CheckTradeSocialProgress()2136 void Empire::CheckTradeSocialProgress()
2137 { m_resource_pools[RE_TRADE]->SetStockpile(m_resource_pools[RE_TRADE]->TotalAvailable()); }
2138
SetColor(const GG::Clr & color)2139 void Empire::SetColor(const GG::Clr& color)
2140 { m_color = color; }
2141
SetName(const std::string & name)2142 void Empire::SetName(const std::string& name)
2143 { m_name = name; }
2144
SetPlayerName(const std::string & player_name)2145 void Empire::SetPlayerName(const std::string& player_name)
2146 { m_player_name = player_name; }
2147
InitResourcePools()2148 void Empire::InitResourcePools() {
2149 // get this empire's owned resource centers and ships (which can both produce resources)
2150 std::vector<int> res_centers;
2151 res_centers.reserve(Objects().ExistingResourceCenters().size());
2152 for (const auto& entry : Objects().ExistingResourceCenters()) {
2153 if (!entry.second->OwnedBy(m_id))
2154 continue;
2155 res_centers.push_back(entry.first);
2156 }
2157 for (const auto& entry : Objects().ExistingShips()) {
2158 if (!entry.second->OwnedBy(m_id))
2159 continue;
2160 res_centers.push_back(entry.first);
2161 }
2162 m_resource_pools[RE_RESEARCH]->SetObjects(res_centers);
2163 m_resource_pools[RE_INDUSTRY]->SetObjects(res_centers);
2164 m_resource_pools[RE_TRADE]->SetObjects(res_centers);
2165
2166 // get this empire's owned population centers
2167 std::vector<int> pop_centers;
2168 pop_centers.reserve(Objects().ExistingPopCenters().size());
2169 for (const auto& entry : Objects().ExistingPopCenters()) {
2170 if (entry.second->OwnedBy(m_id))
2171 pop_centers.push_back(entry.first);
2172 }
2173 m_population_pool.SetPopCenters(pop_centers);
2174
2175
2176 // inform the blockadeable resource pools about systems that can share
2177 m_resource_pools[RE_INDUSTRY]->SetConnectedSupplyGroups(GetSupplyManager().ResourceSupplyGroups(m_id));
2178
2179 // set non-blockadeable resource pools to share resources between all systems
2180 std::set<std::set<int>> sets_set;
2181 std::set<int> all_systems_set;
2182 for (const auto& entry : Objects().ExistingSystems()) {
2183 all_systems_set.insert(entry.first);
2184 }
2185 sets_set.insert(all_systems_set);
2186 m_resource_pools[RE_RESEARCH]->SetConnectedSupplyGroups(sets_set);
2187 m_resource_pools[RE_TRADE]->SetConnectedSupplyGroups(sets_set);
2188 }
2189
UpdateResourcePools()2190 void Empire::UpdateResourcePools() {
2191 // updating queues, allocated_rp, distribution and growth each update their
2192 // respective pools, (as well as the ways in which the resources are used,
2193 // which needs to be done simultaneously to keep things consistent)
2194 UpdateResearchQueue();
2195 UpdateProductionQueue();
2196 UpdateTradeSpending();
2197 UpdatePopulationGrowth();
2198 }
2199
UpdateResearchQueue()2200 void Empire::UpdateResearchQueue() {
2201 m_resource_pools[RE_RESEARCH]->Update();
2202 m_research_queue.Update(m_resource_pools[RE_RESEARCH]->TotalAvailable(), m_research_progress);
2203 m_resource_pools[RE_RESEARCH]->ChangedSignal();
2204 }
2205
UpdateProductionQueue()2206 void Empire::UpdateProductionQueue() {
2207 DebugLogger() << "========= Production Update for empire: " << EmpireID() << " ========";
2208
2209 m_resource_pools[RE_INDUSTRY]->Update();
2210 m_production_queue.Update();
2211 m_resource_pools[RE_INDUSTRY]->ChangedSignal();
2212 }
2213
UpdateTradeSpending()2214 void Empire::UpdateTradeSpending() {
2215 m_resource_pools[RE_TRADE]->Update(); // recalculate total trade production
2216 m_resource_pools[RE_TRADE]->ChangedSignal();
2217 }
2218
UpdatePopulationGrowth()2219 void Empire::UpdatePopulationGrowth()
2220 { m_population_pool.Update(); }
2221
ResetMeters()2222 void Empire::ResetMeters() {
2223 for (auto& entry : m_meters) {
2224 entry.second.ResetCurrent();
2225 }
2226 }
2227
UpdateOwnedObjectCounters()2228 void Empire::UpdateOwnedObjectCounters() {
2229 // ships of each species and design
2230 m_species_ships_owned.clear();
2231 m_ship_designs_owned.clear();
2232 for (const auto& entry : Objects().ExistingShips()) {
2233 if (!entry.second->OwnedBy(this->EmpireID()))
2234 continue;
2235 auto ship = std::dynamic_pointer_cast<const Ship>(entry.second);
2236 if (!ship)
2237 continue;
2238 if (!ship->SpeciesName().empty())
2239 m_species_ships_owned[ship->SpeciesName()]++;
2240 m_ship_designs_owned[ship->DesignID()]++;
2241 }
2242
2243 // ships in the queue for which production started
2244 m_ship_designs_in_production.clear();
2245 for (const auto& elem : m_production_queue) {
2246 ProductionQueue::ProductionItem item = elem.item;
2247
2248 if ((item.build_type == BT_SHIP) && (elem.progress > 0.0f)) {
2249 m_ship_designs_in_production[item.design_id] += elem.blocksize;
2250 }
2251 }
2252
2253 // update ship part counts
2254 m_ship_parts_owned.clear();
2255 m_ship_part_class_owned.clear();
2256 for (const auto& design_count : m_ship_designs_owned) {
2257 const ShipDesign* design = GetShipDesign(design_count.first);
2258 if (!design)
2259 continue;
2260
2261 // update count of ShipParts
2262 for (const auto& ship_part : design->ShipPartCount())
2263 m_ship_parts_owned[ship_part.first] += ship_part.second * design_count.second;
2264
2265 // update count of ShipPartClasses
2266 for (const auto& part_class : design->PartClassCount())
2267 m_ship_part_class_owned[part_class.first] += part_class.second * design_count.second;
2268 }
2269
2270 // colonies of each species, and unspecified outposts
2271 m_species_colonies_owned.clear();
2272 m_outposts_owned = 0;
2273 for (const auto& entry : Objects().ExistingPlanets()) {
2274 if (!entry.second->OwnedBy(this->EmpireID()))
2275 continue;
2276 auto planet = std::dynamic_pointer_cast<const Planet>(entry.second);
2277 if (!planet)
2278 continue;
2279 if (planet->SpeciesName().empty())
2280 m_outposts_owned++;
2281 else
2282 m_species_colonies_owned[planet->SpeciesName()]++;
2283 }
2284
2285 // buildings of each type
2286 m_building_types_owned.clear();
2287 for (const auto& entry : Objects().ExistingBuildings()) {
2288 if (!entry.second->OwnedBy(this->EmpireID()))
2289 continue;
2290 auto building = std::dynamic_pointer_cast<const Building>(entry.second);
2291 if (!building)
2292 continue;
2293 m_building_types_owned[building->BuildingTypeName()]++;
2294 }
2295 }
2296
SetAuthenticated(bool authenticated)2297 void Empire::SetAuthenticated(bool authenticated /*= true*/)
2298 { m_authenticated = authenticated; }
2299
TotalShipsOwned() const2300 int Empire::TotalShipsOwned() const {
2301 // sum up counts for each ship design owned by this empire
2302 // (not using species ship counts, as an empire could potentially own a
2303 // ship that has no species...)
2304 int counter = 0;
2305 for (const auto& entry : m_ship_designs_owned)
2306 { counter += entry.second; }
2307 return counter;
2308 }
2309
RecordShipShotDown(const Ship & ship)2310 void Empire::RecordShipShotDown(const Ship& ship) {
2311 m_empire_ships_destroyed[ship.Owner()]++;
2312 m_ship_designs_destroyed[ship.DesignID()]++;
2313 m_species_ships_destroyed[ship.SpeciesName()]++;
2314 }
2315
RecordShipLost(const Ship & ship)2316 void Empire::RecordShipLost(const Ship& ship) {
2317 m_species_ships_lost[ship.SpeciesName()]++;
2318 m_ship_designs_lost[ship.DesignID()]++;
2319 }
2320
RecordShipScrapped(const Ship & ship)2321 void Empire::RecordShipScrapped(const Ship& ship) {
2322 m_ship_designs_scrapped[ship.DesignID()]++;
2323 m_species_ships_scrapped[ship.SpeciesName()]++;
2324 }
2325
RecordBuildingScrapped(const Building & building)2326 void Empire::RecordBuildingScrapped(const Building& building) {
2327 m_building_types_scrapped[building.BuildingTypeName()]++;
2328 }
2329
RecordPlanetInvaded(const Planet & planet)2330 void Empire::RecordPlanetInvaded(const Planet& planet) {
2331 m_species_planets_invaded[planet.SpeciesName()]++;
2332 }
2333
RecordPlanetDepopulated(const Planet & planet)2334 void Empire::RecordPlanetDepopulated(const Planet& planet) {
2335 m_species_planets_depoped[planet.SpeciesName()]++;
2336 }
2337
TotalShipPartsOwned() const2338 int Empire::TotalShipPartsOwned() const {
2339 // sum counts of all ship parts owned by this empire
2340 int retval = 0;
2341
2342 for (const auto& part_class : m_ship_part_class_owned)
2343 retval += part_class.second;
2344
2345 return retval;
2346 }
2347
TotalBuildingsOwned() const2348 int Empire::TotalBuildingsOwned() const {
2349 // sum up counts for each building type owned by this empire
2350 int counter = 0;
2351 for (const auto& entry : m_building_types_owned)
2352 { counter += entry.second; }
2353 return counter;
2354 }
2355