1 #include "Universe.h"
2
3 #include "../util/OptionsDB.h"
4 #include "../util/i18n.h"
5 #include "../util/Logger.h"
6 #include "../util/Random.h"
7 #include "../util/ScopedTimer.h"
8 #include "../util/CheckSums.h"
9 #include "../util/GameRules.h"
10 #include "../Empire/Empire.h"
11 #include "../Empire/EmpireManager.h"
12 #include "IDAllocator.h"
13 #include "Building.h"
14 #include "BuildingType.h"
15 #include "Effect.h"
16 #include "Fleet.h"
17 #include "FleetPlan.h"
18 #include "Planet.h"
19 #include "Ship.h"
20 #include "System.h"
21 #include "Field.h"
22 #include "FieldType.h"
23 #include "UniverseObject.h"
24 #include "UnlockableItem.h"
25 #include "Predicates.h"
26 #include "ShipPart.h"
27 #include "ShipHull.h"
28 #include "ShipDesign.h"
29 #include "Special.h"
30 #include "Species.h"
31 #include "Tech.h"
32 #include "Conditions.h"
33 #include "ValueRef.h"
34 #include "Enums.h"
35 #include "Pathfinder.h"
36 #include "Encyclopedia.h"
37
38 #include <boost/property_map/property_map.hpp>
39 #include <boost/algorithm/string.hpp>
40 #if BOOST_VERSION >= 106600
41 # include <boost/asio/thread_pool.hpp>
42 # include <boost/asio/post.hpp>
43 #else
44 namespace boost { namespace asio {
45 struct thread_pool {
thread_poolboost::asio::thread_pool46 thread_pool(int) {}
joinboost::asio::thread_pool47 void join() {}
48 };
post(thread_pool,std::function<void ()> func)49 void post(thread_pool, std::function<void()> func) { func(); }
50 } }
51 #endif
52
53 FO_COMMON_API extern const int INVALID_DESIGN_ID;
54
55 namespace {
56 DeclareThreadSafeLogger(effects);
57 DeclareThreadSafeLogger(conditions);
58 }
59
60 #if defined(_MSC_VER)
61 # if (_MSC_VER == 1900)
62 namespace boost {
get_pointer(const volatile Universe * p)63 const volatile Universe* get_pointer(const volatile Universe* p) { return p; }
64 }
65 # endif
66 #endif
67
68 namespace {
69 const bool ENABLE_VISIBILITY_EMPIRE_MEMORY = true; // toggles using memory with visibility, so that empires retain knowledge of objects viewed on previous turns
70
AddOptions(OptionsDB & db)71 void AddOptions(OptionsDB& db) {
72 auto HardwareThreads = []() -> int {
73 int cores = std::thread::hardware_concurrency();
74 return cores > 0 ? cores : 4;
75 };
76
77 db.Add("effects.ui.threads", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_UI_DESC"), HardwareThreads(), RangedValidator<int>(1, 32));
78 db.Add("effects.ai.threads", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_AI_DESC"), 2, RangedValidator<int>(1, 32));
79 db.Add("effects.server.threads", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_SERVER_DESC"), HardwareThreads(), RangedValidator<int>(1, 32));
80 db.Add("effects.accounting.enabled", UserStringNop("OPTIONS_DB_EFFECT_ACCOUNTING"), true, Validator<bool>());
81 }
82 bool temp_bool = RegisterOptions(&AddOptions);
83
AddRules(GameRules & rules)84 void AddRules(GameRules& rules) {
85 // makes all PRNG be reseeded frequently
86 rules.Add<bool>("RULE_RESEED_PRNG_SERVER", "RULE_RESEED_PRNG_SERVER_DESC",
87 "", true, true);
88 rules.Add<bool>("RULE_STARLANES_EVERYWHERE","RULE_STARLANES_EVERYWHERE_DESC",
89 "TEST", false, true);
90 }
91 bool temp_bool2 = RegisterGameRules(&AddRules);
92
93
94 // the effective distance for ships travelling along a wormhole, for
95 // determining how much of their speed is consumed by the jump
96 // unused variable const double WORMHOLE_TRAVEL_DISTANCE = 0.1;
97
98 template <typename Key, typename Value>
99 struct constant_property
100 { Value m_value; };
101 }
102
103 namespace boost {
104 template <typename Key, typename Value>
105 struct property_traits<constant_property<Key, Value>> {
106 typedef Value value_type;
107 typedef Key key_type;
108 typedef readable_property_map_tag category;
109 };
110 template <typename Key, typename Value>
get(const constant_property<Key,Value> & pmap,const Key &)111 const Value& get(const constant_property<Key, Value>& pmap, const Key&) { return pmap.m_value; }
112 }
113
114
115 extern FO_COMMON_API const int ALL_EMPIRES = -1;
116
117 /////////////////////////////////////////////
118 // class Universe
119 /////////////////////////////////////////////
Universe()120 Universe::Universe() :
121 m_pathfinder(std::make_shared<Pathfinder>()),
122 m_universe_width(1000.0),
123 m_inhibit_universe_object_signals(false),
124 m_encoding_empire(ALL_EMPIRES),
125 m_all_objects_visible(false),
126 m_object_id_allocator(new IDAllocator(ALL_EMPIRES, std::vector<int>(), INVALID_OBJECT_ID,
127 TEMPORARY_OBJECT_ID, INVALID_OBJECT_ID)),
128 m_design_id_allocator(new IDAllocator(ALL_EMPIRES, std::vector<int>(), INVALID_DESIGN_ID,
129 TEMPORARY_OBJECT_ID, INVALID_DESIGN_ID))
130 {}
131
~Universe()132 Universe::~Universe()
133 { Clear(); }
134
Clear()135 void Universe::Clear() {
136 // empty object maps
137 m_objects.clear();
138
139 ResetAllIDAllocation();
140
141 m_marked_destroyed.clear();
142 m_destroyed_object_ids.clear();
143
144 // clean up ship designs
145 for (auto& entry : m_ship_designs)
146 delete entry.second;
147 m_ship_designs.clear();
148
149 m_empire_object_visibility.clear();
150 m_empire_object_visibility_turns.clear();
151 m_empire_object_visible_specials.clear();
152
153 m_empire_known_destroyed_object_ids.clear();
154 m_empire_latest_known_objects.clear();
155 m_empire_stale_knowledge_object_ids.clear();
156 m_empire_known_ship_design_ids.clear();
157
158 m_effect_accounting_map.clear();
159 m_effect_discrepancy_map.clear();
160 m_effect_specified_empire_object_visibilities.clear();
161
162 m_stat_records.clear();
163
164 m_universe_width = 1000.0;
165
166 m_pathfinder = std::make_shared<Pathfinder>();
167 }
168
ResetAllIDAllocation(const std::vector<int> & empire_ids)169 void Universe::ResetAllIDAllocation(const std::vector<int>& empire_ids) {
170
171 // Find the highest already allocated id for saved games that did not partition ids by client
172 int highest_allocated_id = INVALID_OBJECT_ID;
173 for (const auto& obj: m_objects.all())
174 highest_allocated_id = std::max(highest_allocated_id, obj->ID());
175
176 *m_object_id_allocator = IDAllocator(ALL_EMPIRES, empire_ids, INVALID_OBJECT_ID,
177 TEMPORARY_OBJECT_ID, highest_allocated_id);
178
179 // Find the highest already allocated id for saved games that did not partition ids by client
180 int highest_allocated_design_id = INVALID_DESIGN_ID;
181 for (const auto& id_and_obj: m_ship_designs)
182 highest_allocated_design_id = std::max(highest_allocated_design_id, id_and_obj.first);
183
184 *m_design_id_allocator = IDAllocator(ALL_EMPIRES, empire_ids, INVALID_DESIGN_ID,
185 TEMPORARY_OBJECT_ID, highest_allocated_design_id);
186
187 DebugLogger() << "Reset id allocators with highest object id = " << highest_allocated_id
188 << " and highest design id = " << highest_allocated_design_id;
189 }
190
SetInitiallyUnlockedItems(Pending::Pending<std::vector<UnlockableItem>> && future)191 void Universe::SetInitiallyUnlockedItems(Pending::Pending<std::vector<UnlockableItem>>&& future)
192 { m_pending_items = std::move(future); }
193
InitiallyUnlockedItems() const194 const std::vector<UnlockableItem>& Universe::InitiallyUnlockedItems() const
195 { return Pending::SwapPending(m_pending_items, m_unlocked_items); }
196
SetInitiallyUnlockedBuildings(Pending::Pending<std::vector<UnlockableItem>> && future)197 void Universe::SetInitiallyUnlockedBuildings(Pending::Pending<std::vector<UnlockableItem>>&& future)
198 { m_pending_buildings = std::move(future); }
199
InitiallyUnlockedBuildings() const200 const std::vector<UnlockableItem>& Universe::InitiallyUnlockedBuildings() const
201 { return Pending::SwapPending(m_pending_buildings, m_unlocked_buildings); }
202
SetInitiallyUnlockedFleetPlans(Pending::Pending<std::vector<std::unique_ptr<FleetPlan>>> && future)203 void Universe::SetInitiallyUnlockedFleetPlans(Pending::Pending<std::vector<std::unique_ptr<FleetPlan>>>&& future)
204 { m_pending_fleet_plans = std::move(future);}
205
InitiallyUnlockedFleetPlans() const206 const std::vector<FleetPlan*> Universe::InitiallyUnlockedFleetPlans() const {
207 Pending::SwapPending(m_pending_fleet_plans, m_unlocked_fleet_plans);
208 std::vector<FleetPlan*> retval;
209 for (const auto& plan : m_unlocked_fleet_plans)
210 retval.push_back(plan.get());
211 return retval;
212 }
213
SetMonsterFleetPlans(Pending::Pending<std::vector<std::unique_ptr<MonsterFleetPlan>>> && future)214 void Universe::SetMonsterFleetPlans(Pending::Pending<std::vector<std::unique_ptr<MonsterFleetPlan>>>&& future)
215 { m_pending_monster_fleet_plans = std::move(future); }
216
MonsterFleetPlans() const217 const std::vector<MonsterFleetPlan*> Universe::MonsterFleetPlans() const {
218 Pending::SwapPending(m_pending_monster_fleet_plans, m_monster_fleet_plans);
219 std::vector<MonsterFleetPlan*> retval;
220 for (const auto& plan : m_monster_fleet_plans)
221 retval.push_back(plan.get());
222 return retval;
223 }
224
SetEmpireStats(Pending::Pending<EmpireStatsMap> future)225 void Universe::SetEmpireStats(Pending::Pending<EmpireStatsMap> future)
226 { m_pending_empire_stats = std::move(future); }
227
EmpireStats() const228 const Universe::EmpireStatsMap& Universe::EmpireStats() const
229 { return Pending::SwapPending(m_pending_empire_stats, m_empire_stats); }
230
EmpireKnownObjects(int empire_id) const231 const ObjectMap& Universe::EmpireKnownObjects(int empire_id) const {
232 if (empire_id == ALL_EMPIRES)
233 return m_objects;
234
235 auto it = m_empire_latest_known_objects.find(empire_id);
236 if (it != m_empire_latest_known_objects.end())
237 return it->second;
238
239 static const ObjectMap const_empty_map;
240 return const_empty_map;
241 }
242
EmpireKnownObjects(int empire_id)243 ObjectMap& Universe::EmpireKnownObjects(int empire_id) {
244 if (empire_id == ALL_EMPIRES)
245 return m_objects;
246
247 auto it = m_empire_latest_known_objects.find(empire_id);
248 if (it != m_empire_latest_known_objects.end())
249 return it->second;
250
251 static ObjectMap empty_map;
252 empty_map.clear();
253 return empty_map;
254 }
255
EmpireVisibleObjectIDs(int empire_id) const256 std::set<int> Universe::EmpireVisibleObjectIDs(int empire_id/* = ALL_EMPIRES*/) const {
257 std::set<int> retval;
258
259 // get id(s) of all empires to consider visibility of...
260 std::set<int> empire_ids;
261 if (empire_id != ALL_EMPIRES)
262 empire_ids.insert(empire_id);
263 else
264 for (const auto& empire_entry : Empires())
265 empire_ids.insert(empire_entry.first);
266
267 // check each object's visibility against all empires, including the object
268 // if an empire has visibility of it
269 for (const auto& obj : m_objects.all()) {
270 for (int detector_empire_id : empire_ids) {
271 Visibility vis = GetObjectVisibilityByEmpire(obj->ID(), detector_empire_id);
272 if (vis >= VIS_BASIC_VISIBILITY) {
273 retval.insert(obj->ID());
274 break;
275 }
276 }
277 }
278
279 return retval;
280 }
281
DestroyedObjectIds() const282 const std::set<int>& Universe::DestroyedObjectIds() const
283 { return m_destroyed_object_ids; }
284
HighestDestroyedObjectID() const285 int Universe::HighestDestroyedObjectID() const {
286 if (m_destroyed_object_ids.empty())
287 return INVALID_OBJECT_ID;
288 return *m_destroyed_object_ids.rbegin();
289 }
290
EmpireKnownDestroyedObjectIDs(int empire_id) const291 const std::set<int>& Universe::EmpireKnownDestroyedObjectIDs(int empire_id) const {
292 auto it = m_empire_known_destroyed_object_ids.find(empire_id);
293 if (it != m_empire_known_destroyed_object_ids.end())
294 return it->second;
295 return m_destroyed_object_ids;
296 }
297
EmpireStaleKnowledgeObjectIDs(int empire_id) const298 const std::set<int>& Universe::EmpireStaleKnowledgeObjectIDs(int empire_id) const {
299 auto it = m_empire_stale_knowledge_object_ids.find(empire_id);
300 if (it != m_empire_stale_knowledge_object_ids.end())
301 return it->second;
302 static const std::set<int> empty_set;
303 return empty_set;
304 }
305
GetShipDesign(int ship_design_id) const306 const ShipDesign* Universe::GetShipDesign(int ship_design_id) const {
307 if (ship_design_id == INVALID_DESIGN_ID)
308 return nullptr;
309 ship_design_iterator it = m_ship_designs.find(ship_design_id);
310 return (it != m_ship_designs.end() ? it->second : nullptr);
311 }
312
RenameShipDesign(int design_id,const std::string & name,const std::string & description)313 void Universe::RenameShipDesign(int design_id, const std::string& name/* = ""*/,
314 const std::string& description/* = ""*/)
315 {
316 auto design_it = m_ship_designs.find(design_id);
317 if (design_it == m_ship_designs.end()) {
318 DebugLogger() << "Universe::RenameShipDesign tried to rename a ship design that doesn't exist!";
319 return;
320 }
321 ShipDesign* design = design_it->second;
322
323 design->SetName(name);
324 design->SetDescription(description);
325 }
326
GetGenericShipDesign(const std::string & name) const327 const ShipDesign* Universe::GetGenericShipDesign(const std::string& name) const {
328 if (name.empty())
329 return nullptr;
330 for (const auto& entry : m_ship_designs) {
331 const ShipDesign* design = entry.second;
332 const std::string& design_name = design->Name(false);
333 if (name == design_name)
334 return design;
335 }
336 return nullptr;
337 }
338
EmpireKnownShipDesignIDs(int empire_id) const339 const std::set<int>& Universe::EmpireKnownShipDesignIDs(int empire_id) const {
340 auto it = m_empire_known_ship_design_ids.find(empire_id);
341 if (it != m_empire_known_ship_design_ids.end())
342 return it->second;
343 static const std::set<int> empty_set;
344 return empty_set;
345 }
346
GetObjectVisibilityByEmpire(int object_id,int empire_id) const347 Visibility Universe::GetObjectVisibilityByEmpire(int object_id, int empire_id) const {
348 if (empire_id == ALL_EMPIRES || GetUniverse().AllObjectsVisible())
349 return VIS_FULL_VISIBILITY;
350
351 auto empire_it = m_empire_object_visibility.find(empire_id);
352 if (empire_it == m_empire_object_visibility.end())
353 return VIS_NO_VISIBILITY;
354
355 const ObjectVisibilityMap& vis_map = empire_it->second;
356
357 auto vis_map_it = vis_map.find(object_id);
358 if (vis_map_it == vis_map.end())
359 return VIS_NO_VISIBILITY;
360
361 return vis_map_it->second;
362 }
363
GetObjectVisibilityTurnMapByEmpire(int object_id,int empire_id) const364 const Universe::VisibilityTurnMap& Universe::GetObjectVisibilityTurnMapByEmpire(int object_id, int empire_id) const {
365 static const std::map<Visibility, int> empty_map;
366
367 auto empire_it = m_empire_object_visibility_turns.find(empire_id);
368 if (empire_it == m_empire_object_visibility_turns.end())
369 return empty_map;
370
371 const ObjectVisibilityTurnMap& obj_vis_turn_map = empire_it->second;
372 auto object_it = obj_vis_turn_map.find(object_id);
373 if (object_it == obj_vis_turn_map.end())
374 return empty_map;
375
376 return object_it->second;
377 }
378
GetObjectVisibleSpecialsByEmpire(int object_id,int empire_id) const379 std::set<std::string> Universe::GetObjectVisibleSpecialsByEmpire(int object_id, int empire_id) const {
380 if (empire_id != ALL_EMPIRES) {
381 auto empire_it = m_empire_object_visible_specials.find(empire_id);
382 if (empire_it == m_empire_object_visible_specials.end())
383 return std::set<std::string>();
384 const ObjectSpecialsMap& object_specials_map = empire_it->second;
385 auto object_it = object_specials_map.find(object_id);
386 if (object_it == object_specials_map.end())
387 return std::set<std::string>();
388 return object_it->second;
389 } else {
390 auto obj = m_objects.get(object_id);
391 if (!obj)
392 return std::set<std::string>();
393 // all specials visible
394 std::set<std::string> retval;
395 for (const auto& entry : obj->Specials()) {
396 retval.insert(entry.first);
397 }
398 return retval;
399 }
400 }
401
GenerateObjectID()402 int Universe::GenerateObjectID() {
403 auto new_id = m_object_id_allocator->NewID();
404 return new_id;
405 }
406
GenerateDesignID()407 int Universe::GenerateDesignID() {
408 auto new_id = m_design_id_allocator->NewID();
409 return new_id;
410 }
411
ObfuscateIDGenerator()412 void Universe::ObfuscateIDGenerator() {
413 m_object_id_allocator->ObfuscateBeforeSerialization();
414 m_design_id_allocator->ObfuscateBeforeSerialization();
415 }
416
VerifyUnusedObjectID(const int empire_id,const int id)417 bool Universe::VerifyUnusedObjectID(const int empire_id, const int id) {
418 auto good_id_and_possible_legacy = m_object_id_allocator->IsIDValidAndUnused(id, empire_id);
419 if (!good_id_and_possible_legacy.second) // Possibly from old save game
420 ErrorLogger() << "object id = " << id << " should not have been assigned by empire = " << empire_id;
421
422 return good_id_and_possible_legacy.first && good_id_and_possible_legacy.second;
423 }
424
InsertIDCore(std::shared_ptr<UniverseObject> obj,int id)425 void Universe::InsertIDCore(std::shared_ptr<UniverseObject> obj, int id) {
426 if (!obj)
427 return;
428
429 auto valid = m_object_id_allocator->UpdateIDAndCheckIfOwned(id);
430 if (!valid) {
431 ErrorLogger() << "An object has not been inserted into the universe because it's id = " << id << " is invalid.";
432 obj->SetID(INVALID_OBJECT_ID);
433 return;
434 }
435
436 obj->SetID(id);
437 m_objects.insert(std::forward<std::shared_ptr<UniverseObject>>(obj));
438 }
439
InsertShipDesign(ShipDesign * ship_design)440 bool Universe::InsertShipDesign(ShipDesign* ship_design) {
441 if (!ship_design
442 || (ship_design->ID() != INVALID_DESIGN_ID && m_ship_designs.count(ship_design->ID())))
443 { return false; }
444
445 return InsertShipDesignID(ship_design, boost::none, GenerateDesignID());
446 }
447
InsertShipDesignID(ShipDesign * ship_design,boost::optional<int> empire_id,int id)448 bool Universe::InsertShipDesignID(ShipDesign* ship_design, boost::optional<int> empire_id, int id) {
449 if (!ship_design)
450 return false;
451
452 if (!m_design_id_allocator->UpdateIDAndCheckIfOwned(id)) {
453 ErrorLogger() << "Ship design id " << id << " is invalid.";
454 return false;
455 }
456
457 if (m_ship_designs.count(id)) {
458 ErrorLogger() << "Ship design id " << id << " already exists.";
459 return false;
460 }
461 ship_design->SetID(id);
462 m_ship_designs[id] = ship_design;
463 return true;
464 }
465
DeleteShipDesign(int design_id)466 bool Universe::DeleteShipDesign(int design_id) {
467 auto it = m_ship_designs.find(design_id);
468 if (it != m_ship_designs.end()) {
469 m_ship_designs.erase(it);
470 return true;
471 } else { return false; }
472 }
473
ResetAllObjectMeters(bool target_max_unpaired,bool active)474 void Universe::ResetAllObjectMeters(bool target_max_unpaired, bool active) {
475 for (const auto& object : m_objects.all()) {
476 if (target_max_unpaired)
477 object->ResetTargetMaxUnpairedMeters();
478 if (active)
479 object->ResetPairedActiveMeters();
480 }
481 }
482
ResetObjectMeters(const std::vector<std::shared_ptr<UniverseObject>> & objects,bool target_max_unpaired,bool active)483 void Universe::ResetObjectMeters(const std::vector<std::shared_ptr<UniverseObject>>& objects,
484 bool target_max_unpaired, bool active)
485 {
486 for (const auto& object : objects) {
487 if (target_max_unpaired)
488 object->ResetTargetMaxUnpairedMeters();
489 if (active)
490 object->ResetPairedActiveMeters();
491 }
492 }
493
ApplyAllEffectsAndUpdateMeters(bool do_accounting)494 void Universe::ApplyAllEffectsAndUpdateMeters(bool do_accounting) {
495 ScopedTimer timer("Universe::ApplyAllEffectsAndUpdateMeters");
496
497 if (do_accounting) {
498 // override if option disabled
499 do_accounting = GetOptionsDB().Get<bool>("effects.accounting.enabled");
500 }
501
502 m_effect_specified_empire_object_visibilities.clear();
503
504 // cache all activation and scoping condition results before applying
505 // Effects, since the application of these Effects may affect the activation
506 // and scoping evaluations
507 std::map<int, Effect::SourcesEffectsTargetsAndCausesVec> source_effects_targets_causes;
508 GetEffectsAndTargets(source_effects_targets_causes, false);
509
510 // revert all current meter values (which are modified by effects) to
511 // their initial state for this turn, so that max/target/unpaired meter
512 // value can be calculated (by accumulating all effects' modifications this
513 // turn) and active meters have the proper baseline from which to
514 // accumulate changes from effects
515 ResetAllObjectMeters(true, true);
516 for (auto& entry : Empires())
517 entry.second->ResetMeters();
518
519 ExecuteEffects(source_effects_targets_causes, do_accounting, false, false, true);
520 // clamp max meters to [DEFAULT_VALUE, LARGE_VALUE] and current meters to [DEFAULT_VALUE, max]
521 // clamp max and target meters to [DEFAULT_VALUE, LARGE_VALUE] and current meters to [DEFAULT_VALUE, max]
522 for (const auto& object : m_objects.all())
523 object->ClampMeters();
524 }
525
ApplyMeterEffectsAndUpdateMeters(const std::vector<int> & object_ids,bool do_accounting)526 void Universe::ApplyMeterEffectsAndUpdateMeters(const std::vector<int>& object_ids, bool do_accounting) {
527 if (object_ids.empty())
528 return;
529 ScopedTimer timer("Universe::ApplyMeterEffectsAndUpdateMeters on " + std::to_string(object_ids.size()) + " objects");
530 if (do_accounting) {
531 // override if disabled
532 do_accounting = GetOptionsDB().Get<bool>("effects.accounting.enabled");
533 }
534 // cache all activation and scoping condition results before applying Effects, since the application of
535 // these Effects may affect the activation and scoping evaluations
536 std::map<int, Effect::SourcesEffectsTargetsAndCausesVec> source_effects_targets_causes;
537 GetEffectsAndTargets(source_effects_targets_causes, object_ids, true);
538
539 std::vector<std::shared_ptr<UniverseObject>> objects = m_objects.find(object_ids);
540
541 // revert all current meter values (which are modified by effects) to
542 // their initial state for this turn, so meter
543 // value can be calculated (by accumulating all effects' modifications this
544 // turn) and active meters have the proper baseline from which to
545 // accumulate changes from effects
546 ResetObjectMeters(objects, true, true);
547 // could also reset empire meters here, but unless all objects have meters
548 // recalculated, some targets that lead to empire meters being modified may
549 // be missed, and estimated empire meters would be inaccurate
550
551 ExecuteEffects(source_effects_targets_causes, do_accounting, true);
552
553 for (auto& object : objects)
554 object->ClampMeters();
555 }
556
ApplyMeterEffectsAndUpdateMeters(bool do_accounting)557 void Universe::ApplyMeterEffectsAndUpdateMeters(bool do_accounting) {
558 ScopedTimer timer("Universe::ApplyMeterEffectsAndUpdateMeters on all objects");
559 if (do_accounting) {
560 // override if disabled
561 do_accounting = GetOptionsDB().Get<bool>("effects.accounting.enabled");
562 }
563
564 std::map<int, Effect::SourcesEffectsTargetsAndCausesVec> source_effects_targets_causes;
565 GetEffectsAndTargets(source_effects_targets_causes, true);
566
567 TraceLogger(effects) << "Universe::ApplyMeterEffectsAndUpdateMeters resetting...";
568 for (const auto& object : m_objects.all()) {
569 TraceLogger(effects) << "object " << object->Name() << " (" << object->ID() << ") before resetting meters: ";
570 for (auto const& meter_pair : object->Meters()) {
571 TraceLogger(effects) << " meter: " << meter_pair.first
572 << " value: " << meter_pair.second.Current();
573 }
574 object->ResetTargetMaxUnpairedMeters();
575 object->ResetPairedActiveMeters();
576 TraceLogger(effects) << "object " << object->Name() << " (" << object->ID() << ") after resetting meters: ";
577 for (auto const& meter_pair : object->Meters()) {
578 TraceLogger(effects) << " meter: " << meter_pair.first
579 << " value: " << meter_pair.second.Current();
580 }
581 }
582 for (auto& entry : Empires())
583 entry.second->ResetMeters();
584 ExecuteEffects(source_effects_targets_causes, do_accounting, true, false, true);
585
586 for (const auto& object : m_objects.all())
587 object->ClampMeters();
588 }
589
ApplyAppearanceEffects(const std::vector<int> & object_ids)590 void Universe::ApplyAppearanceEffects(const std::vector<int>& object_ids) {
591 if (object_ids.empty())
592 return;
593 ScopedTimer timer("Universe::ApplyAppearanceEffects on " + std::to_string(object_ids.size()) + " objects");
594
595 // cache all activation and scoping condition results before applying
596 // Effects, since the application of these Effects may affect the
597 // activation and scoping evaluations
598 std::map<int, Effect::SourcesEffectsTargetsAndCausesVec> source_effects_targets_causes;
599 GetEffectsAndTargets(source_effects_targets_causes, object_ids, false);
600 ExecuteEffects(source_effects_targets_causes, false, false, true);
601 }
602
ApplyAppearanceEffects()603 void Universe::ApplyAppearanceEffects() {
604 ScopedTimer timer("Universe::ApplyAppearanceEffects on all objects");
605
606 // cache all activation and scoping condition results before applying
607 // Effects, since the application of Effects in general (even if not these
608 // particular Effects) may affect the activation and scoping evaluations
609 std::map<int, Effect::SourcesEffectsTargetsAndCausesVec> source_effects_targets_causes;
610 GetEffectsAndTargets(source_effects_targets_causes, false);
611 ExecuteEffects(source_effects_targets_causes, false, false, true);
612 }
613
ApplyGenerateSitRepEffects()614 void Universe::ApplyGenerateSitRepEffects() {
615 ScopedTimer timer("Universe::ApplyGenerateSitRepEffects on all objects");
616
617 // cache all activation and scoping condition results before applying
618 // Effects, since the application of Effects in general (even if not these
619 // particular Effects) may affect the activation and scoping evaluations
620 std::map<int, Effect::SourcesEffectsTargetsAndCausesVec> source_effects_targets_causes;
621 GetEffectsAndTargets(source_effects_targets_causes, false);
622 ExecuteEffects(source_effects_targets_causes, false, false, false, false, true);
623 }
624
InitMeterEstimatesAndDiscrepancies()625 void Universe::InitMeterEstimatesAndDiscrepancies() {
626 DebugLogger(effects) << "Universe::InitMeterEstimatesAndDiscrepancies";
627 ScopedTimer timer("Universe::InitMeterEstimatesAndDiscrepancies", true, std::chrono::microseconds(1));
628
629 // clear old discrepancies and accounting
630 m_effect_discrepancy_map.clear();
631 m_effect_accounting_map.clear();
632 m_effect_discrepancy_map.reserve(m_objects.size());
633 m_effect_accounting_map.reserve(m_objects.size());
634
635 TraceLogger(effects) << "IMEAD: updating meter estimates";
636
637 // save starting meter vales
638 DiscrepancyMap starting_current_meter_values;
639 starting_current_meter_values.reserve(m_objects.size());
640 for (const auto& obj : m_objects.all()) {
641 auto& obj_discrep = starting_current_meter_values[obj->ID()];
642 obj_discrep.reserve(obj->Meters().size());
643 for (const auto& meter_pair : obj->Meters()) {
644 // inserting in order into initially-empty map should always put next item efficiently at end
645 obj_discrep.emplace_hint(obj_discrep.end(), meter_pair.first, meter_pair.second.Current());
646 }
647 }
648
649
650 // generate new estimates (normally uses discrepancies, but in this case will find none)
651 UpdateMeterEstimates();
652
653
654 TraceLogger(effects) << "IMEAD: determining discrepancies";
655 TraceLogger(effects) << "Initial accounting map size: " << m_effect_accounting_map.size()
656 << " and discrepancy map size: " << m_effect_discrepancy_map.size();
657
658 // determine meter max discrepancies
659 for (auto& entry : m_effect_accounting_map) {
660 int object_id = entry.first;
661 // skip destroyed objects
662 if (m_destroyed_object_ids.count(object_id))
663 continue;
664 // get object
665 auto obj = m_objects.get(object_id);
666 if (!obj) {
667 ErrorLogger(effects) << "Universe::InitMeterEstimatesAndDiscrepancies couldn't find an object that was in the effect accounting map...?";
668 continue;
669 }
670 if (obj->Meters().empty())
671 continue;
672
673 TraceLogger(effects) << "... discrepancies for " << obj->Name() << " (" << obj->ID() << "):";
674
675 auto& account_map = entry.second;
676 account_map.reserve(obj->Meters().size());
677
678 // discrepancies should be empty before this loop, so emplacing / assigning should be fine here (without overwriting existing data)
679 auto dis_map_it = m_effect_discrepancy_map.emplace_hint(m_effect_discrepancy_map.end(), object_id, boost::container::flat_map<MeterType, double>{});
680 auto& discrep_map = dis_map_it->second;
681 discrep_map.reserve(obj->Meters().size());
682
683 auto& start_map = starting_current_meter_values[object_id];
684 start_map.reserve(obj->Meters().size());
685
686 TraceLogger(effects) << "For object " << object_id << " initial accounting map size: "
687 << account_map.size() << " discrep map size: " << discrep_map.size()
688 << " and starting meters map size: " << start_map.size();
689
690 // every meter has a value at the start of the turn, and a value after
691 // updating with known effects
692 for (auto& meter_pair : obj->Meters()) {
693 MeterType type = meter_pair.first;
694 // skip paired active meters, as differences in these are expected and persistent, and not a "discrepancy"
695 if (type >= METER_POPULATION && type <= METER_TROOPS)
696 continue;
697 Meter& meter = meter_pair.second;
698
699 // discrepancy is the difference between expected and actual meter
700 // values at start of turn. here "expected" is what the meter value
701 // was before updating the meters, and actual is what it is now
702 // after updating the meters based on the known universe.
703 float discrepancy = start_map[type] - meter.Current();
704 if (discrepancy == 0.0f) continue; // no discrepancy for this meter
705
706 // add to discrepancy map. as above, should have been empty before this loop.
707 discrep_map.emplace_hint(discrep_map.end(), type, discrepancy);
708
709 // correct current max meter estimate for discrepancy
710 meter.AddToCurrent(discrepancy);
711
712 // add discrepancy adjustment to meter accounting
713 account_map[type].emplace_back(INVALID_OBJECT_ID, ECT_UNKNOWN_CAUSE, discrepancy, meter.Current());
714
715 TraceLogger(effects) << "... ... " << type << ": " << discrepancy;
716 }
717 }
718 }
719
UpdateMeterEstimates()720 void Universe::UpdateMeterEstimates()
721 { UpdateMeterEstimates(GetOptionsDB().Get<bool>("effects.accounting.enabled")); }
722
UpdateMeterEstimates(bool do_accounting)723 void Universe::UpdateMeterEstimates(bool do_accounting) {
724 for (int obj_id : m_objects.FindExistingObjectIDs())
725 m_effect_accounting_map[obj_id].clear();
726 // update meters for all objects.
727 UpdateMeterEstimatesImpl(std::vector<int>(), do_accounting);
728 }
729
UpdateMeterEstimates(int object_id,bool update_contained_objects)730 void Universe::UpdateMeterEstimates(int object_id, bool update_contained_objects) {
731 // ids of the object and all valid contained objects
732 std::unordered_set<int> collected_ids;
733
734 // Collect objects ids to update meter for. This may be a single object, a
735 // group of related objects. Return true if all collected ids are valid.
736 std::function<bool (int, int)> collect_ids =
737 [this, &collected_ids, update_contained_objects, &collect_ids]
738 (int cur_id, int container_id)
739 {
740 // Ignore if already in the set
741 if (collected_ids.count(cur_id))
742 return true;
743
744 auto cur_object = m_objects.get(cur_id);
745 if (!cur_object) {
746 ErrorLogger() << "Universe::UpdateMeterEstimates tried to get an invalid object for id " << cur_id
747 << " in container " << container_id
748 << ". All meter estimates will be updated.";
749 UpdateMeterEstimates();
750 return false;
751 }
752
753 // add object
754 collected_ids.insert(cur_id);
755
756 // add contained objects to list of objects to process, if requested.
757 if (update_contained_objects)
758 for (const auto& contained_id : cur_object->ContainedObjectIDs())
759 if (!collect_ids(contained_id, cur_id))
760 return false;
761 return true;
762 };
763
764 if (!collect_ids(object_id, INVALID_OBJECT_ID))
765 return;
766
767 if (collected_ids.empty())
768 return;
769
770 // Clear ids that will be updated
771 for (auto cur_id : collected_ids)
772 m_effect_accounting_map[cur_id].clear();
773
774 // Convert to a vector
775 std::vector<int> objects_vec;
776 objects_vec.reserve(collected_ids.size());
777 std::copy(collected_ids.begin(), collected_ids.end(), std::back_inserter(objects_vec));
778 UpdateMeterEstimatesImpl(objects_vec, GetOptionsDB().Get<bool>("effects.accounting.enabled"));
779 }
780
UpdateMeterEstimates(const std::vector<int> & objects_vec)781 void Universe::UpdateMeterEstimates(const std::vector<int>& objects_vec) {
782 std::set<int> objects_set; // ensures no duplicates
783
784 for (int object_id : objects_vec) {
785 // skip destroyed objects
786 if (m_destroyed_object_ids.count(object_id))
787 continue;
788 m_effect_accounting_map[object_id].clear();
789 objects_set.insert(object_id);
790 }
791 std::vector<int> final_objects_vec;
792 final_objects_vec.reserve(objects_set.size());
793 std::copy(objects_set.begin(), objects_set.end(), std::back_inserter(final_objects_vec));
794 if (!final_objects_vec.empty())
795 UpdateMeterEstimatesImpl(final_objects_vec, GetOptionsDB().Get<bool>("effects.accounting.enabled"));
796 }
797
UpdateMeterEstimatesImpl(const std::vector<int> & objects_vec,bool do_accounting)798 void Universe::UpdateMeterEstimatesImpl(const std::vector<int>& objects_vec, bool do_accounting) {
799 auto number_text = std::to_string(objects_vec.empty() ? m_objects.ExistingObjects().size() : objects_vec.size());
800 ScopedTimer timer("Universe::UpdateMeterEstimatesImpl on " + number_text + " objects", true);
801
802 // get all pointers to objects once, to avoid having to do so repeatedly
803 // when iterating over the list in the following code
804 auto object_ptrs = m_objects.find(objects_vec);
805 if (objects_vec.empty()) {
806 object_ptrs.reserve(m_objects.ExistingObjects().size());
807 std::transform(m_objects.ExistingObjects().begin(), m_objects.ExistingObjects().end(),
808 std::back_inserter(object_ptrs), [](const std::map<int, std::shared_ptr<const UniverseObject>>::value_type& p) {
809 return std::const_pointer_cast<UniverseObject>(p.second);
810 });
811 }
812
813 for (auto& obj : object_ptrs) {
814 // Reset max meters to DEFAULT_VALUE and current meters to initial value
815 // at start of this turn
816 obj->ResetTargetMaxUnpairedMeters();
817 obj->ResetPairedActiveMeters();
818
819 if (!do_accounting)
820 continue;
821
822 auto& meters = obj->Meters();
823 auto& account_map = m_effect_accounting_map[obj->ID()];
824 account_map.clear(); // remove any old accounting info. this should be redundant here.
825 account_map.reserve(meters.size());
826
827 for (auto& meter_pair : meters) {
828 MeterType type = meter_pair.first;
829 const auto& meter = meter_pair.second;
830 float meter_change = meter.Current() - Meter::DEFAULT_VALUE;
831 if (meter_change != 0.0f)
832 account_map[type].emplace_back(INVALID_OBJECT_ID, ECT_INHERENT, meter_change, meter.Current());
833 }
834 }
835
836 TraceLogger(effects) << "UpdateMeterEstimatesImpl after resetting meters objects:";
837 for (auto& obj : object_ptrs)
838 TraceLogger(effects) << obj->Dump();
839
840 // cache all activation and scoping condition results before applying Effects, since the application of
841 // these Effects may affect the activation and scoping evaluations
842 std::map<int, Effect::SourcesEffectsTargetsAndCausesVec> source_effects_targets_causes;
843 GetEffectsAndTargets(source_effects_targets_causes, objects_vec, true);
844
845 // Apply and record effect meter adjustments
846 ExecuteEffects(source_effects_targets_causes, do_accounting, true, false, false, false);
847
848 TraceLogger(effects) << "UpdateMeterEstimatesImpl after executing effects objects:";
849 for (auto& obj : object_ptrs)
850 TraceLogger(effects) << obj->Dump();
851
852 // Apply known discrepancies between expected and calculated meter maxes at start of turn. This
853 // accounts for the unknown effects on the meter, and brings the estimate in line with the actual
854 // max at the start of the turn
855 if (!m_effect_discrepancy_map.empty() && do_accounting) {
856 for (auto& obj : object_ptrs) {
857 // check if this object has any discrepancies
858 auto dis_it = m_effect_discrepancy_map.find(obj->ID());
859 if (dis_it == m_effect_discrepancy_map.end())
860 continue; // no discrepancy, so skip to next object
861
862 auto& account_map = m_effect_accounting_map[obj->ID()]; // reserving space now should be redundant with previous manipulations
863
864 // apply all meters' discrepancies
865 for (auto& entry : dis_it->second) {
866 MeterType type = entry.first;
867 double discrepancy = entry.second;
868
869 //if (discrepancy == 0.0) continue;
870
871 Meter* meter = obj->GetMeter(type);
872 if (!meter)
873 continue;
874
875 TraceLogger(effects) << "object " << obj->ID() << " has meter " << type
876 << ": discrepancy: " << discrepancy << " and : " << meter->Dump();
877
878 meter->AddToCurrent(discrepancy);
879
880 account_map[type].emplace_back(INVALID_OBJECT_ID, ECT_UNKNOWN_CAUSE, discrepancy, meter->Current());
881 }
882 }
883 }
884
885 // clamp meters to valid range of max values, and so current is less than max
886 for (auto& obj : object_ptrs) {
887 // currently this clamps all meters, even if not all meters are being processed by this function...
888 // but that shouldn't be a problem, as clamping meters that haven't changed since they were last
889 // updated should have no effect
890 obj->ClampMeters();
891 }
892
893 TraceLogger(effects) << "UpdateMeterEstimatesImpl after discrepancies and clamping objects:";
894 for (auto& obj : object_ptrs)
895 TraceLogger(effects) << obj->Dump();
896 }
897
BackPropagateObjectMeters()898 void Universe::BackPropagateObjectMeters() {
899 for (const auto& obj : m_objects.all())
900 obj->BackPropagateMeters();
901 }
902
903 namespace {
904 /** Evaluate activation, and scope conditions of \a effects_group for
905 * each of the objects in \a source_objects for the candidate target
906 * objects in \a candidate_objects_in (unless it is empty, in which case
907 * the default candidate objects for the scope condition is used. Stores
908 * the objects that matched the effects group's scope condition, for each
909 * source object, as a separate entry in \a targets_cases_out */
StoreTargetsAndCausesOfEffectsGroup(const ObjectMap & object_map,const Effect::EffectsGroup * effects_group,const Condition::ObjectSet & source_objects,EffectsCauseType effect_cause_type,const std::string & specific_cause_name,const std::unordered_set<int> & candidate_object_ids,Effect::TargetSet & candidate_objects_in,Effect::SourcesEffectsTargetsAndCausesVec & source_effects_targets_causes_out,int n)910 void StoreTargetsAndCausesOfEffectsGroup(
911 const ObjectMap& object_map,
912 const Effect::EffectsGroup* effects_group,
913 const Condition::ObjectSet& source_objects,
914 EffectsCauseType effect_cause_type,
915 const std::string& specific_cause_name,
916 const std::unordered_set<int>& candidate_object_ids, // TODO: Can this be removed along with scope is source test?
917 Effect::TargetSet& candidate_objects_in, // may be empty: indicates to test for full universe of objects
918 Effect::SourcesEffectsTargetsAndCausesVec& source_effects_targets_causes_out,
919 int n)
920 {
921 TraceLogger(effects) << "StoreTargetsAndCausesOfEffectsGroup < " << n << " >"
922 << " cause type: " << effect_cause_type
923 << " specific cause: " << specific_cause_name << " )";
924
925 auto scope = effects_group->Scope();
926 if (!scope)
927 return;
928 bool scope_is_just_source = dynamic_cast<Condition::Source*>(scope);
929
930 auto message{"StoreTargetsAndCausesOfEffectsGroup < " + std::to_string(n) + " >"
931 + " cause type: " + boost::lexical_cast<std::string>(effect_cause_type)
932 + " specific cause: " + specific_cause_name
933 + " sources: " + std::to_string(source_objects.size())
934 + " scope: " + boost::algorithm::erase_all_copy(effects_group->Scope()->Dump(), "\n")};
935
936 ScopedTimer timer(message, std::chrono::milliseconds(20));
937
938 source_effects_targets_causes_out.reserve(source_objects.size());
939 ScriptingContext source_context(object_map);
940
941 for (auto& source : source_objects) {
942 // assuming input sources objects set was already filtered with activation condition
943 source_context.source = source;
944 // construct output in-place
945
946 // SourcedEffectsGroup {int source_object_id; const EffectsGroup* effects_group;}
947 // EffectCause {EffectsCauseType cause_type; std::string specific_cause; std::string custom_label; }
948 // TargetsAndCause {TargetSet target_set; EffectCause effect_cause;}
949 // typedef std::vector<std::pair<SourcedEffectsGroup, TargetsAndCause>> SourcesEffectsTargetsAndCausesVec;
950 source_effects_targets_causes_out.emplace_back(
951 Effect::SourcedEffectsGroup{source->ID(), effects_group},
952 Effect::TargetsAndCause{
953 {}, // empty Effect::TargetSet
954 Effect::EffectCause{effect_cause_type, specific_cause_name, effects_group->AccountingLabel()}});
955
956 // extract output Effect::TargetSet
957 Effect::TargetSet& matched_targets{source_effects_targets_causes_out.back().second.target_set};
958
959 // move scope condition matches into output matches
960 if (candidate_objects_in.empty()) {
961 // condition default candidates will be tested
962 scope->Eval(source_context, matched_targets);
963
964 } else if (scope_is_just_source) {
965 // special case for condition that is just Source when a set of
966 // candidates is specified: only need to put the source in if
967 // it is in the candidates
968 if (candidate_object_ids.count(source->ID()))
969 matched_targets.push_back(std::const_pointer_cast<UniverseObject>(source));
970
971 } else {
972 // input candidates will all be tested
973 scope->Eval(source_context, matched_targets, candidate_objects_in);
974 }
975
976 TraceLogger(effects) << "Scope Results " << n << " source: " << source->ID()
977 << " matches: " << [&]() {
978 std::stringstream ss;
979 for (auto& obj : matched_targets)
980 ss << obj->ID() << ", ";
981 return ss.str();
982 }();
983 }
984 }
985
986
987 /** Collect info for scope condition evaluations and dispatch those
988 * evaluations to \a thread_pool. Not thread-safe, but the individual
989 * condition evaluations should be safe to evaluate in parallel. */
DispatchEffectsGroupScopeEvaluations(EffectsCauseType effect_cause_type,const std::string & specific_cause_name,const Condition::ObjectSet & source_objects,const std::vector<std::shared_ptr<Effect::EffectsGroup>> & effects_groups,bool only_meter_effects,const ObjectMap & object_map,const Condition::ObjectSet & potential_targets,const std::unordered_set<int> & potential_target_ids,std::list<std::pair<Effect::SourcesEffectsTargetsAndCausesVec,Effect::SourcesEffectsTargetsAndCausesVec * >> & source_effects_targets_causes_reorder_buffer_out,boost::asio::thread_pool & thread_pool,int & n)990 void DispatchEffectsGroupScopeEvaluations(
991 EffectsCauseType effect_cause_type,
992 const std::string& specific_cause_name,
993 const Condition::ObjectSet& source_objects,
994 const std::vector<std::shared_ptr<Effect::EffectsGroup>>& effects_groups,
995 bool only_meter_effects,
996 const ObjectMap& object_map,
997 const Condition::ObjectSet& potential_targets,
998 const std::unordered_set<int>& potential_target_ids,
999 std::list<std::pair<Effect::SourcesEffectsTargetsAndCausesVec,
1000 Effect::SourcesEffectsTargetsAndCausesVec*>>& source_effects_targets_causes_reorder_buffer_out,
1001 boost::asio::thread_pool& thread_pool,
1002 int& n)
1003 {
1004 std::vector<std::pair<Condition::Condition*, int>> already_evaluated_activation_condition_idx;
1005 already_evaluated_activation_condition_idx.reserve(effects_groups.size());
1006
1007 TraceLogger(effects) << "Checking activation condition for " << source_objects.size()
1008 << " sources and "
1009 << (potential_targets.empty() ? "full universe" : std::to_string(potential_targets.size()))
1010 << " potential targets";
1011
1012 // evaluate activation conditions of effects_groups on input source objects
1013 std::vector<Condition::ObjectSet> active_sources{effects_groups.size()};
1014 ScriptingContext source_context{object_map};
1015 for (std::size_t i = 0; i < effects_groups.size(); ++i) {
1016 const auto* effects_group = effects_groups.at(i).get();
1017 if (only_meter_effects && !effects_group->HasMeterEffects())
1018 continue;
1019 if (!effects_group->Scope())
1020 continue;
1021
1022 if (!effects_group->Activation()) {
1023 // no activation condition, leave all sources active
1024 active_sources[i] = source_objects;
1025 continue;
1026
1027 } else {
1028 // check if this activation condition has already been evaluated
1029 bool cache_hit = false;
1030 for (const auto& cond_idx : already_evaluated_activation_condition_idx) {
1031 if (*cond_idx.first == *(effects_group->Activation())) {
1032 active_sources[i] = active_sources[cond_idx.second]; // copy previous condition evaluation result
1033 cache_hit = true;
1034 break;
1035 }
1036 }
1037 if (cache_hit)
1038 continue; // don't need to evaluate activation condition on these sources again
1039
1040 // no cache hit; need to evaluate activation condition on input source objects
1041 if (effects_group->Activation()->SourceInvariant()) {
1042 // can apply condition to all source objects simultaneously
1043 Condition::ObjectSet rejected;
1044 rejected.reserve(source_objects.size());
1045 active_sources[i] = source_objects; // copy input source objects set
1046 source_context.source = nullptr;
1047 effects_group->Activation()->Eval(source_context, active_sources[i], rejected, Condition::MATCHES);
1048
1049 } else {
1050 // need to apply separately to each source object
1051 active_sources[i].reserve(source_objects.size());
1052 for (auto& obj : source_objects) {
1053 source_context.source = obj;
1054 if (effects_group->Activation()->Eval(source_context, obj))
1055 active_sources[i].push_back(obj);
1056 }
1057 }
1058
1059 // save evaluation lookup index in cache
1060 already_evaluated_activation_condition_idx.emplace_back(effects_group->Activation(), i);
1061 }
1062 }
1063
1064
1065 TraceLogger(effects) << "After activation condition, for " << effects_groups.size() << " effects groups "
1066 << "have # sources: " << [&active_sources]()
1067 {
1068 std::stringstream ss;
1069 for (auto& src_set : active_sources)
1070 ss << src_set.size() << ", ";
1071 return ss.str();
1072 }();
1073
1074
1075 // TODO: is it faster to index by scope and activation condition or scope and filtered sources set?
1076 std::vector<std::tuple<Condition::Condition*,
1077 Condition::ObjectSet,
1078 Effect::SourcesEffectsTargetsAndCausesVec*>>
1079 already_dispatched_scope_condition_ptrs;
1080 already_evaluated_activation_condition_idx.reserve(effects_groups.size());
1081
1082
1083 // duplicate input ObjectSet potential_targets as local TargetSet
1084 // that can be passed to StoreTargetsAndCausesOfEffectsGroup
1085 Effect::TargetSet potential_targets_copy;
1086 potential_targets_copy.reserve(potential_targets.size());
1087 for (const auto& obj : potential_targets)
1088 potential_targets_copy.emplace_back(std::const_pointer_cast<UniverseObject>(obj));
1089
1090
1091 // evaluate scope conditions for source objects that are active
1092 for (std::size_t i = 0; i < effects_groups.size(); ++i) {
1093 if (active_sources[i].empty()) {
1094 TraceLogger(effects) << "Skipping empty active sources set";
1095 continue;
1096 }
1097 TraceLogger(effects) << "Handing active sources set of size: " << active_sources[i].size();
1098
1099 const auto* effects_group = effects_groups.at(i).get();
1100 n++;
1101
1102 // allocate space to store output of effectsgroup targets evaluation
1103 // for the sources and this effects group
1104 source_effects_targets_causes_reorder_buffer_out.emplace_back();
1105 source_effects_targets_causes_reorder_buffer_out.back().second = nullptr; // default, may be overwritten
1106
1107
1108 // check if the scope-condition + sources set has already been dispatched
1109 bool cache_hit = false;
1110 if (effects_group->Scope()) {
1111 for (const auto& cond_sources_ptr : already_dispatched_scope_condition_ptrs) {
1112 if (*std::get<0>(cond_sources_ptr) == *(effects_group->Scope()) &&
1113 std::get<1>(cond_sources_ptr) == active_sources[i])
1114 {
1115 TraceLogger(effects) << "scope condition cache hit !";
1116
1117 // record pointer to previously-dispatched result struct
1118 // that will contain the results to copy later, after
1119 // all dispatched condition evauations have resolved
1120 source_effects_targets_causes_reorder_buffer_out.back().second =
1121 std::get<2>(cond_sources_ptr);
1122
1123 // allocate result structs that contain empty
1124 // Effect::TargetSets that will be filled later
1125 auto& vec_out{source_effects_targets_causes_reorder_buffer_out.back().first};
1126 for (auto& source : active_sources[i]) {
1127 source_context.source = source;
1128 vec_out.emplace_back(
1129 Effect::SourcedEffectsGroup{source->ID(), effects_group},
1130 Effect::TargetsAndCause{
1131 {}, // empty Effect::TargetSet
1132 Effect::EffectCause{effect_cause_type, specific_cause_name,
1133 effects_group->AccountingLabel()}});
1134 }
1135
1136 cache_hit = true;
1137 break;
1138 }
1139 }
1140 }
1141 if (cache_hit)
1142 continue;
1143 if (!cache_hit) {
1144 TraceLogger(effects) << "scope condition cache miss idx: " << n;
1145
1146 // add cache entry for this combination, with pointer to the
1147 // storage that will contain the to-be-dispatched scope
1148 // condition evaluation results
1149 already_dispatched_scope_condition_ptrs.emplace_back(
1150 effects_group->Scope(), active_sources[i],
1151 &source_effects_targets_causes_reorder_buffer_out.back().first);
1152 }
1153
1154
1155 TraceLogger(effects) << "Dispatching Scope Evaluations < " << n << " > sources: " << [&]() {
1156 std::stringstream ss;
1157 for (auto& obj : active_sources[i])
1158 ss << obj->ID() << ", ";
1159 return ss.str();
1160 }()
1161 << " cause type: " << effect_cause_type
1162 << " specific cause: " << specific_cause_name
1163 << " candidates: " << [&]() {
1164 std::stringstream ss;
1165 for (auto& obj : potential_targets)
1166 ss << obj->ID() << ", ";
1167 return ss.str();
1168 }();
1169
1170 // asynchronously evaluate targetset for effectsgroup for each source using worker threads
1171 boost::asio::post(
1172 thread_pool,
1173 [
1174 &object_map,
1175 effects_group,
1176 active_source_objects{active_sources[i]},
1177 effect_cause_type,
1178 specific_cause_name,
1179 &potential_target_ids,
1180 potential_targets_copy, // by value, not reference, so each dispatched call has independent input TargetSet
1181 &source_effects_targets_causes_vec_out = source_effects_targets_causes_reorder_buffer_out.back().first,
1182 n
1183 ]() mutable
1184 {
1185 StoreTargetsAndCausesOfEffectsGroup(object_map, effects_group, active_source_objects,
1186 effect_cause_type, specific_cause_name,
1187 potential_target_ids, potential_targets_copy,
1188 source_effects_targets_causes_vec_out, n);
1189 });
1190 }
1191 }
1192 } // namespace
1193
GetEffectsAndTargets(std::map<int,Effect::SourcesEffectsTargetsAndCausesVec> & source_effects_targets_causes,bool only_meter_effects) const1194 void Universe::GetEffectsAndTargets(std::map<int, Effect::SourcesEffectsTargetsAndCausesVec>& source_effects_targets_causes,
1195 bool only_meter_effects) const
1196 {
1197 source_effects_targets_causes.clear();
1198 GetEffectsAndTargets(source_effects_targets_causes, std::vector<int>(), only_meter_effects);
1199 }
1200
GetEffectsAndTargets(std::map<int,Effect::SourcesEffectsTargetsAndCausesVec> & source_effects_targets_causes,const std::vector<int> & target_object_ids,bool only_meter_effects) const1201 void Universe::GetEffectsAndTargets(std::map<int, Effect::SourcesEffectsTargetsAndCausesVec>& source_effects_targets_causes,
1202 const std::vector<int>& target_object_ids,
1203 bool only_meter_effects) const
1204 {
1205 SectionedScopedTimer type_timer("Effect TargetSets Evaluation", std::chrono::microseconds(0));
1206
1207 // assemble target objects from input vector of IDs
1208 Condition::ObjectSet potential_targets{m_objects.find(target_object_ids)};
1209 std::unordered_set<int> potential_ids_set{target_object_ids.begin(), target_object_ids.end()};
1210
1211 TraceLogger(effects) << "GetEffectsAndTargets input candidate target objects:";
1212 for (auto& obj : potential_targets)
1213 TraceLogger(effects) << obj->Dump();
1214
1215
1216 // list, not vector, to avoid invaliding iterators when pushing more items
1217 // onto list due to vector reallocation.
1218 // .first are results of evaluating an effectsgroups's activation and source
1219 // conditions for a set of candidate source objects
1220 // .second may be nullptr, in which case it is ignored, or may be a pointer
1221 // to another earlier entry in this list, which contains the results of
1222 // evaluating the same scope condition on the same set of activation-passing
1223 // source objects, and which should be copied into the paired Vec
1224 std::list<std::pair<Effect::SourcesEffectsTargetsAndCausesVec,
1225 Effect::SourcesEffectsTargetsAndCausesVec*>> source_effects_targets_causes_reorder_buffer;
1226
1227 const unsigned int num_threads = static_cast<unsigned int>(std::max(1, EffectsProcessingThreads()));
1228 boost::asio::thread_pool thread_pool(num_threads);
1229
1230 int n = 1; // count dispatched condition evaluations
1231
1232
1233 // 1) EffectsGroups from Species
1234 type_timer.EnterSection("species");
1235 TraceLogger(effects) << "Universe::GetEffectsAndTargets for SPECIES";
1236 std::map<std::string, std::vector<std::shared_ptr<const UniverseObject>>> species_objects;
1237 // find each species planets in single pass, maintaining object map order per-species
1238 for (auto& planet : m_objects.all<Planet>()) {
1239 if (m_destroyed_object_ids.count(planet->ID()))
1240 continue;
1241 const std::string& species_name = planet->SpeciesName();
1242 if (species_name.empty())
1243 continue;
1244 const Species* species = GetSpecies(species_name);
1245 if (!species) {
1246 ErrorLogger() << "GetEffectsAndTargets couldn't get Species " << species_name;
1247 continue;
1248 }
1249 species_objects[species_name].push_back(planet);
1250 }
1251 // find each species ships in single pass, maintaining object map order per-species
1252 for (auto& ship : m_objects.all<Ship>()) {
1253 if (m_destroyed_object_ids.count(ship->ID()))
1254 continue;
1255 const std::string& species_name = ship->SpeciesName();
1256 if (species_name.empty())
1257 continue;
1258 const Species* species = GetSpecies(species_name);
1259 if (!species) {
1260 ErrorLogger() << "GetEffectsAndTargets couldn't get Species " << species_name;
1261 continue;
1262 }
1263 species_objects[species_name].push_back(ship);
1264 }
1265 // allocate storage for target sets and dispatch condition evaluations
1266 for (const auto& entry : GetSpeciesManager()) {
1267 const std::string& species_name = entry.first;
1268 const auto& species = entry.second;
1269 auto species_objects_it = species_objects.find(species_name);
1270 if (species_objects_it == species_objects.end())
1271 continue;
1272 const auto& source_objects = species_objects_it->second;
1273 if (source_objects.empty())
1274 continue;
1275
1276 DispatchEffectsGroupScopeEvaluations(ECT_SPECIES, species_name,
1277 source_objects, species->Effects(),
1278 only_meter_effects,
1279 m_objects, potential_targets,
1280 potential_ids_set,
1281 source_effects_targets_causes_reorder_buffer,
1282 thread_pool, n);
1283 }
1284
1285
1286 // 2) EffectsGroups from Specials
1287 type_timer.EnterSection("specials");
1288 TraceLogger(effects) << "Universe::GetEffectsAndTargets for SPECIALS";
1289 std::map<std::string, std::vector<std::shared_ptr<const UniverseObject>>> specials_objects;
1290 // determine objects with specials in a single pass
1291 for (const auto& obj : m_objects.all()) {
1292 if (m_destroyed_object_ids.count(obj->ID()))
1293 continue;
1294 for (const auto& entry : obj->Specials()) {
1295 const std::string& special_name = entry.first;
1296 const Special* special = GetSpecial(special_name);
1297 if (!special) {
1298 ErrorLogger() << "GetEffectsAndTargets couldn't get Special " << special_name;
1299 continue;
1300 }
1301 specials_objects[special_name].push_back(obj);
1302 }
1303 }
1304 // dispatch condition evaluations
1305 for (const std::string& special_name : SpecialNames()) {
1306 const Special* special = GetSpecial(special_name);
1307 auto specials_objects_it = specials_objects.find(special_name);
1308 if (specials_objects_it == specials_objects.end())
1309 continue;
1310 const auto& source_objects = specials_objects_it->second;
1311 if (source_objects.empty())
1312 continue;
1313
1314 DispatchEffectsGroupScopeEvaluations(ECT_SPECIAL, special_name,
1315 source_objects, special->Effects(),
1316 only_meter_effects,
1317 m_objects, potential_targets,
1318 potential_ids_set,
1319 source_effects_targets_causes_reorder_buffer,
1320 thread_pool, n);
1321 }
1322
1323
1324 // 3) EffectsGroups from Techs
1325 type_timer.EnterSection("techs");
1326 TraceLogger(effects) << "Universe::GetEffectsAndTargets for TECHS";
1327 std::list<Condition::ObjectSet> tech_sources; // for each empire, a set with a single source object for all its techs
1328 // select a source object for each empire and dispatch condition evaluations
1329 for (auto& entry : Empires()) {
1330 const Empire* empire = entry.second;
1331 auto source = empire->Source();
1332 if (!source)
1333 continue;
1334
1335 // unlike species and special effectsgroups, all techs for an empire have the same source object
1336 tech_sources.emplace_back(Condition::ObjectSet{1U, source});
1337 const auto& source_objects = tech_sources.back();
1338
1339 for (const auto tech_entry : empire->ResearchedTechs()) {
1340 const std::string& tech_name{tech_entry.first};
1341 const Tech* tech = GetTech(tech_name);
1342 if (!tech) continue;
1343
1344 DispatchEffectsGroupScopeEvaluations(ECT_TECH, tech_name,
1345 source_objects, tech->Effects(),
1346 only_meter_effects,
1347 m_objects, potential_targets,
1348 potential_ids_set,
1349 source_effects_targets_causes_reorder_buffer,
1350 thread_pool, n);
1351 }
1352 }
1353
1354
1355 // 4) EffectsGroups from Buildings
1356 type_timer.EnterSection("buildings");
1357 TraceLogger(effects) << "Universe::GetEffectsAndTargets for BUILDINGS";
1358 // determine buildings of each type in a single pass
1359 std::map<std::string, std::vector<std::shared_ptr<const UniverseObject>>> buildings_by_type;
1360 for (auto& building : m_objects.all<Building>()) {
1361 if (m_destroyed_object_ids.count(building->ID()))
1362 continue;
1363 const std::string& building_type_name = building->BuildingTypeName();
1364 const BuildingType* building_type = GetBuildingType(building_type_name);
1365 if (!building_type) {
1366 ErrorLogger() << "GetEffectsAndTargets couldn't get BuildingType " << building->BuildingTypeName();
1367 continue;
1368 }
1369
1370 buildings_by_type[building_type_name].push_back(building);
1371 }
1372 // dispatch condition evaluations
1373 for (const auto& entry : GetBuildingTypeManager()) {
1374 const std::string& building_type_name = entry.first;
1375 const BuildingType* building_type = entry.second.get();
1376 auto buildings_by_type_it = buildings_by_type.find(building_type_name);
1377 if (buildings_by_type_it == buildings_by_type.end())
1378 continue;
1379 const auto& source_objects = buildings_by_type_it->second;
1380 if (source_objects.empty())
1381 continue;
1382
1383 DispatchEffectsGroupScopeEvaluations(ECT_BUILDING, building_type_name,
1384 source_objects, building_type->Effects(),
1385 only_meter_effects,
1386 m_objects, potential_targets,
1387 potential_ids_set,
1388 source_effects_targets_causes_reorder_buffer,
1389 thread_pool, n);
1390 }
1391
1392
1393 // 5) EffectsGroups from Ship Hull and Ship Parts
1394 type_timer.EnterSection("ship hull/parts");
1395 TraceLogger(effects) << "Universe::GetEffectsAndTargets for SHIPS hulls and parts";
1396 // determine ship hulls and parts of each type in a single pass
1397 // the same ship might be added multiple times if it contains the part multiple times
1398 // recomputing targets for the same ship and part is kind of silly here, but shouldn't hurt
1399 std::map<std::string, std::vector<std::shared_ptr<const UniverseObject>>> ships_by_ship_hull;
1400 std::map<std::string, std::vector<std::shared_ptr<const UniverseObject>>> ships_by_ship_part;
1401
1402 for (auto& ship : m_objects.all<Ship>()) {
1403 if (m_destroyed_object_ids.count(ship->ID()))
1404 continue;
1405 const ShipDesign* ship_design = ship->Design();
1406 if (!ship_design)
1407 continue;
1408 const ShipHull* ship_hull = GetShipHull(ship_design->Hull());
1409 if (!ship_hull) {
1410 ErrorLogger() << "GetEffectsAndTargets couldn't get ShipHull";
1411 continue;
1412 }
1413 ships_by_ship_hull[ship_hull->Name()].push_back(ship);
1414
1415 for (const std::string& part : ship_design->Parts()) {
1416 if (part.empty())
1417 continue;
1418 const ShipPart* ship_part = GetShipPart(part);
1419 if (!ship_part) {
1420 ErrorLogger() << "GetEffectsAndTargets couldn't get ShipPart";
1421 continue;
1422 }
1423 ships_by_ship_part[part].push_back(ship);
1424 }
1425 }
1426
1427 // dispatch hull condition evaluations
1428 for (const auto& entry : GetShipHullManager()) {
1429 const std::string& ship_hull_name = entry.first;
1430 const auto& ship_hull = entry.second;
1431 auto ships_by_hull_it = ships_by_ship_hull.find(ship_hull_name);
1432 if (ships_by_hull_it == ships_by_ship_hull.end())
1433 continue;
1434 const auto& source_objects = ships_by_hull_it->second;
1435 if (source_objects.empty())
1436 continue;
1437
1438 DispatchEffectsGroupScopeEvaluations(ECT_SHIP_HULL, ship_hull_name,
1439 source_objects, ship_hull->Effects(),
1440 only_meter_effects,
1441 m_objects, potential_targets,
1442 potential_ids_set,
1443 source_effects_targets_causes_reorder_buffer,
1444 thread_pool, n);
1445 }
1446 // dispatch part condition evaluations
1447 for (const auto& entry : GetShipPartManager()) {
1448 const std::string& ship_part_name = entry.first;
1449 const ShipPart* ship_part = entry.second.get();
1450 auto ships_by_ship_part_it = ships_by_ship_part.find(ship_part_name);
1451 if (ships_by_ship_part_it == ships_by_ship_part.end())
1452 continue;
1453 const auto& source_objects = ships_by_ship_part_it->second;
1454 if (source_objects.empty())
1455 continue;
1456
1457 DispatchEffectsGroupScopeEvaluations(ECT_SHIP_PART, ship_part_name,
1458 source_objects, ship_part->Effects(),
1459 only_meter_effects,
1460 m_objects, potential_targets,
1461 potential_ids_set,
1462 source_effects_targets_causes_reorder_buffer,
1463 thread_pool, n);
1464 }
1465
1466
1467 // 6) EffectsGroups from Fields
1468 type_timer.EnterSection("fields");
1469 TraceLogger(effects) << "Universe::GetEffectsAndTargets for FIELDS";
1470 // determine fields of each type in a single pass
1471 std::map<std::string, std::vector<std::shared_ptr<const UniverseObject>>> fields_by_type;
1472 for (auto& field : m_objects.all<Field>()) {
1473 if (m_destroyed_object_ids.count(field->ID()))
1474 continue;
1475 const std::string& field_type_name = field->FieldTypeName();
1476 const FieldType* field_type = GetFieldType(field_type_name);
1477 if (!field_type) {
1478 ErrorLogger() << "GetEffectsAndTargets couldn't get FieldType " << field->FieldTypeName();
1479 continue;
1480 }
1481
1482 fields_by_type[field_type_name].push_back(field);
1483 }
1484
1485 // dispatch field condition evaluations
1486 for (const auto& entry : GetFieldTypeManager()) {
1487 const std::string& field_type_name = entry.first;
1488 const FieldType* field_type = entry.second.get();
1489 auto fields_by_type_it = fields_by_type.find(field_type_name);
1490 if (fields_by_type_it == fields_by_type.end())
1491 continue;
1492 const auto& source_objects = fields_by_type_it->second;
1493 if (source_objects.empty())
1494 continue;
1495
1496 DispatchEffectsGroupScopeEvaluations(ECT_FIELD, field_type_name,
1497 source_objects, field_type->Effects(),
1498 only_meter_effects,
1499 m_objects, potential_targets,
1500 potential_ids_set,
1501 source_effects_targets_causes_reorder_buffer,
1502 thread_pool, n);
1503 }
1504
1505
1506 // wait for evaluation of conditions dispatched above
1507 type_timer.EnterSection("eval waiting");
1508 thread_pool.join();
1509
1510
1511 // add results to source_effects_targets_causes, sorted by effect priority, then in issue order
1512 type_timer.EnterSection("reordering");
1513 for (const auto& job_results : source_effects_targets_causes_reorder_buffer) {
1514 if (job_results.second) {
1515 // entry in reorder buffer contains empty Effect::TargetSets that
1516 // should be populated from the pointed-to earlier entry
1517 const auto& resolved_scope_target_sets{*job_results.second};
1518 TraceLogger(effects) << "Reordering using cached result of size " << resolved_scope_target_sets.size()
1519 << " for expected result of size: " << job_results.first.size();
1520
1521 for (std::size_t i = 0; i < std::min(job_results.first.size(), resolved_scope_target_sets.size()); ++i) {
1522 // create entry in output with empty TargetSet
1523 auto& result{job_results.first[i]};
1524 int priority = result.first.effects_group->Priority();
1525 source_effects_targets_causes[priority].push_back(result);
1526
1527 // overwrite empty placeholder TargetSet with contents of
1528 // pointed-to earlier entry
1529 source_effects_targets_causes[priority].back().second.target_set =
1530 resolved_scope_target_sets.at(i).second.target_set;
1531 }
1532
1533 } else {
1534 // entry in reorder buffer contains the results of an effectsgroup
1535 // scope/activation being evaluatied with a set of source objects
1536 for (const auto& result : job_results.first) {
1537 int priority = result.first.effects_group->Priority();
1538 source_effects_targets_causes[priority].push_back(result);
1539 }
1540 }
1541 }
1542 }
1543
ExecuteEffects(std::map<int,Effect::SourcesEffectsTargetsAndCausesVec> & source_effects_targets_causes,bool update_effect_accounting,bool only_meter_effects,bool only_appearance_effects,bool include_empire_meter_effects,bool only_generate_sitrep_effects)1544 void Universe::ExecuteEffects(std::map<int, Effect::SourcesEffectsTargetsAndCausesVec>& source_effects_targets_causes,
1545 bool update_effect_accounting,
1546 bool only_meter_effects/* = false*/,
1547 bool only_appearance_effects/* = false*/,
1548 bool include_empire_meter_effects/* = false*/,
1549 bool only_generate_sitrep_effects/* = false*/)
1550 {
1551 ScopedTimer timer("Universe::ExecuteEffects", true);
1552
1553 m_marked_destroyed.clear();
1554 std::map<std::string, std::set<int>> executed_nonstacking_effects; // for each stacking group, which objects have had effects executed on them
1555
1556
1557 // within each priority group, execute effects in dispatch order
1558 for (auto& priority_group : source_effects_targets_causes) {
1559 //int priority = priority_group.first;
1560 Effect::SourcesEffectsTargetsAndCausesVec& setc{priority_group.second};
1561
1562 // construct a source context, which is updated for each entry in sources-effects-targets.
1563 // execute each effectsgroup on its target set
1564 ScriptingContext source_context;
1565 for (std::pair<Effect::SourcedEffectsGroup, Effect::TargetsAndCause>& effect_group_entry : setc) {
1566 Effect::TargetsAndCause& targets_and_cause{effect_group_entry.second};
1567 Effect::TargetSet& target_set{targets_and_cause.target_set};
1568
1569 const Effect::EffectsGroup* effects_group = effect_group_entry.first.effects_group;
1570
1571 if (only_meter_effects && !effects_group->HasMeterEffects())
1572 continue;
1573 if (only_appearance_effects && !effects_group->HasAppearanceEffects())
1574 continue;
1575 if (only_generate_sitrep_effects && !effects_group->HasSitrepEffects())
1576 continue;
1577
1578 const std::string& stacking_group{effects_group->StackingGroup()};
1579
1580 // 1) If other EffectsGroups or sources with the same stacking group
1581 // have acted on some of the targets in the scope of the current
1582 // EffectsGroup, skip them.
1583 // 2) Add remaining objects to executed_nonstacking_effects, as effects
1584 // with the starting group are now acting on them
1585 if (!stacking_group.empty()) {
1586 std::set<int>& non_stacking_targets = executed_nonstacking_effects[stacking_group];
1587
1588 // this is a set difference/union algorithm:
1589 // targets -= non_stacking_targets
1590 // non_stacking_targets += targets
1591 for (auto object_it = target_set.begin(); object_it != target_set.end();) {
1592 int object_id = (*object_it)->ID();
1593 auto it = non_stacking_targets.find(object_id);
1594
1595 if (it != non_stacking_targets.end()) {
1596 *object_it = target_set.back();
1597 target_set.pop_back();
1598 } else {
1599 non_stacking_targets.insert(object_id);
1600 ++object_it;
1601 }
1602 }
1603 }
1604
1605 // were all objects in target set removed due to stacking? If so, skip to next effect / source / target set
1606 if (target_set.empty())
1607 continue;
1608
1609 TraceLogger(effects) << "\n\n * * * * * * * * * * * (new effects group log entry)(" << effects_group->TopLevelContent()
1610 << " " << effects_group->AccountingLabel() << " " << effects_group->StackingGroup() << ")";
1611
1612 // execute Effects in the EffectsGroup
1613 source_context.source = source_context.ContextObjects().get(effect_group_entry.first.source_object_id);
1614 effects_group->Execute(source_context,
1615 targets_and_cause,
1616 update_effect_accounting ? &m_effect_accounting_map : nullptr,
1617 only_meter_effects,
1618 only_appearance_effects,
1619 include_empire_meter_effects,
1620 only_generate_sitrep_effects);
1621 }
1622 }
1623
1624 // actually do destroy effect action. Executing the effect just marks
1625 // objects to be destroyed, but doesn't actually do so in order to ensure
1626 // no interaction in order of effects and source or target objects being
1627 // destroyed / deleted between determining target sets and executing effects.
1628 // but, do now collect info about source objects for destruction, to sure
1629 // their info is available even if they are destroyed by the upcoming effect
1630 // destruction
1631 for (auto& entry : m_marked_destroyed) {
1632 int obj_id = entry.first;
1633 auto obj = m_objects.get(obj_id);
1634 if (!obj)
1635 continue;
1636
1637 // recording of what species/empire destroyed what other stuff in
1638 // empire statistics for this destroyed object and any contained objects
1639 for (int destructor : entry.second) {
1640 CountDestructionInStats(obj_id, destructor);
1641 }
1642
1643 for (int contained_obj_id : obj->ContainedObjectIDs()) {
1644 for (int destructor : entry.second) {
1645 CountDestructionInStats(contained_obj_id, destructor);
1646 }
1647 }
1648 // not worried about fleets being deleted because all their ships were
1649 // destroyed... as of this writing there are no stats tracking
1650 // destruction of fleets.
1651
1652 // do actual recursive destruction.
1653 RecursiveDestroy(obj_id);
1654 }
1655 }
1656
CountDestructionInStats(int object_id,int source_object_id)1657 void Universe::CountDestructionInStats(int object_id, int source_object_id) {
1658 auto obj = m_objects.get(object_id);
1659 if (!obj)
1660 return;
1661 auto source = m_objects.get(source_object_id);
1662 if (!source)
1663 return;
1664 if (auto shp = std::dynamic_pointer_cast<const Ship>(obj)) {
1665 if (auto source_empire = GetEmpire(source->Owner()))
1666 source_empire->RecordShipShotDown(*shp);
1667 if (auto obj_empire = GetEmpire(obj->Owner()))
1668 obj_empire->RecordShipLost(*shp);
1669 }
1670 }
1671
SetEffectDerivedVisibility(int empire_id,int object_id,int source_id,const ValueRef::ValueRef<Visibility> * vis)1672 void Universe::SetEffectDerivedVisibility(int empire_id, int object_id, int source_id,
1673 const ValueRef::ValueRef<Visibility>* vis)
1674 {
1675 if (empire_id == ALL_EMPIRES)
1676 return;
1677 if (object_id <= INVALID_OBJECT_ID)
1678 return;
1679 if (!vis)
1680 return;
1681 m_effect_specified_empire_object_visibilities[empire_id][object_id].push_back({source_id, vis});
1682 }
1683
ApplyEffectDerivedVisibilities()1684 void Universe::ApplyEffectDerivedVisibilities() {
1685 EmpireObjectVisibilityMap new_empire_object_visibilities;
1686 // for each empire with a visibility map
1687 for (auto& empire_entry : m_effect_specified_empire_object_visibilities) {
1688 if (empire_entry.first == ALL_EMPIRES)
1689 continue; // can't set a non-empire's visibility
1690 for (const auto& object_entry : empire_entry.second) {
1691 if (object_entry.first <= INVALID_OBJECT_ID)
1692 continue; // can't set a non-object's visibility
1693 auto target = m_objects.get(object_entry.first);
1694 if (!target)
1695 continue; // don't need to set a non-gettable object's visibility
1696
1697 // if already have an entry in new_empire_object_visibilities then
1698 // use that as the target initial visibility for purposes of
1699 // evaluating this ValueRef. If not, use the object's current
1700 // in-universe Visibility for the specified empire
1701 Visibility target_initial_vis =
1702 m_empire_object_visibility[empire_entry.first][object_entry.first];
1703 auto neov_it = new_empire_object_visibilities[empire_entry.first].find(object_entry.first);
1704 if (neov_it != new_empire_object_visibilities[empire_entry.first].end())
1705 target_initial_vis = neov_it->second;
1706
1707 // evaluate valuerefs and and store visibility of object
1708 for (auto& source_ref_entry : object_entry.second) {
1709 // set up context for executing ValueRef to determine visibility to set
1710 auto source = m_objects.get(source_ref_entry.first);
1711 ScriptingContext context(source, target, target_initial_vis, nullptr, nullptr, m_objects);
1712
1713 const auto val_ref = source_ref_entry.second;
1714
1715 // evaluate and store actual new visibility level
1716 Visibility vis = val_ref->Eval(context);
1717 target_initial_vis = vis; // store for next iteration's context
1718 new_empire_object_visibilities[empire_entry.first][object_entry.first] = vis;
1719 }
1720 }
1721 }
1722
1723 // copy newly determined visibility levels into actual gamestate, without
1724 // erasing visibilities that aren't affected by the effects
1725 for (auto empire_entry : new_empire_object_visibilities) {
1726 int empire_id = empire_entry.first;
1727 for (auto object_entry : empire_entry.second) {
1728 int object_id = object_entry.first;
1729 Visibility vis = object_entry.second;
1730 m_empire_object_visibility[empire_id][object_id] = vis;
1731 }
1732 }
1733 }
1734
ForgetKnownObject(int empire_id,int object_id)1735 void Universe::ForgetKnownObject(int empire_id, int object_id) {
1736 // Note: Client calls this with empire_id == ALL_EMPIRES to
1737 // immediately forget information without waiting for the turn update.
1738 ObjectMap& objects(EmpireKnownObjects(empire_id));
1739
1740 if (objects.empty())
1741 return;
1742
1743 auto obj = objects.get(object_id);
1744 if (!obj) {
1745 ErrorLogger() << "ForgetKnownObject empire: " << empire_id
1746 << " bad object id: " << object_id;
1747 return;
1748 }
1749
1750 if (empire_id != ALL_EMPIRES && obj->OwnedBy(empire_id)) {
1751 ErrorLogger() << "ForgetKnownObject empire: " << empire_id
1752 << " object: " << object_id
1753 << ". Trying to forget visibility of own object.";
1754 return;
1755 }
1756
1757 // Remove all contained objects to avoid breaking fleet+ship, system+planet invariants
1758 auto contained_ids = obj->ContainedObjectIDs();
1759 for (int child_id : contained_ids)
1760 ForgetKnownObject(empire_id, child_id);
1761
1762 int container_id = obj->ContainerObjectID();
1763 if (container_id != INVALID_OBJECT_ID) {
1764 if (auto container = objects.get(container_id)) {
1765 if (auto system = std::dynamic_pointer_cast<System>(container))
1766 system->Remove(object_id);
1767 else if (auto planet = std::dynamic_pointer_cast<Planet>(container))
1768 planet->RemoveBuilding(object_id);
1769 else if (auto fleet = std::dynamic_pointer_cast<Fleet>(container)) {
1770 fleet->RemoveShips({object_id});
1771 if (fleet->Empty())
1772 objects.erase(fleet->ID());
1773 }
1774 }
1775 }
1776
1777 objects.erase(object_id);
1778 }
1779
SetEmpireObjectVisibility(int empire_id,int object_id,Visibility vis)1780 void Universe::SetEmpireObjectVisibility(int empire_id, int object_id, Visibility vis) {
1781 if (empire_id == ALL_EMPIRES || object_id == INVALID_OBJECT_ID)
1782 return;
1783
1784 // get visibility map for empire and find object in it
1785 auto& vis_map = m_empire_object_visibility[empire_id];
1786 auto vis_map_it = vis_map.find(object_id);
1787
1788 // if object not already present, store default value (which may be replaced)
1789 if (vis_map_it == vis_map.end()) {
1790 vis_map[object_id] = VIS_NO_VISIBILITY;
1791
1792 // get iterator pointing at newly-created entry
1793 vis_map_it = vis_map.find(object_id);
1794 }
1795
1796 // increase stored value if new visibility is higher than last recorded
1797 if (vis > vis_map_it->second)
1798 vis_map_it->second = vis;
1799
1800 // if object is a ship, empire also gets knowledge of its design
1801 if (vis >= VIS_PARTIAL_VISIBILITY) {
1802 if (auto ship = m_objects.get<Ship>(object_id))
1803 SetEmpireKnowledgeOfShipDesign(ship->DesignID(), empire_id);
1804 }
1805 }
1806
SetEmpireSpecialVisibility(int empire_id,int object_id,const std::string & special_name,bool visible)1807 void Universe::SetEmpireSpecialVisibility(int empire_id, int object_id,
1808 const std::string& special_name,
1809 bool visible/* = true*/)
1810 {
1811 if (empire_id == ALL_EMPIRES || special_name.empty() || object_id == INVALID_OBJECT_ID)
1812 return;
1813 if (visible)
1814 m_empire_object_visible_specials[empire_id][object_id].insert(special_name);
1815 else
1816 m_empire_object_visible_specials[empire_id][object_id].erase(special_name);
1817 }
1818
1819
1820 namespace {
1821 /** for each empire: for each position where the empire has detector objects,
1822 * what is the empire's detection range at that location? (this is the
1823 * largest of the detection ranges of objects the empire has at that spot) */
GetEmpiresPositionDetectionRanges()1824 std::map<int, std::map<std::pair<double, double>, float>> GetEmpiresPositionDetectionRanges() {
1825 std::map<int, std::map<std::pair<double, double>, float>> retval;
1826
1827 for (const auto& obj : Objects().all()) {
1828 // skip unowned objects, which can't provide detection to any empire
1829 if (obj->Unowned())
1830 continue;
1831
1832 // skip objects with no detection range
1833 const Meter* detection_meter = obj->GetMeter(METER_DETECTION);
1834 if (!detection_meter)
1835 continue;
1836 float object_detection_range = detection_meter->Current();
1837 if (object_detection_range <= 0.0f)
1838 continue;
1839
1840 // don't allow moving ships / fleets to give detection
1841 std::shared_ptr<const Fleet> fleet;
1842 if (obj->ObjectType() == OBJ_FLEET) {
1843 fleet = std::dynamic_pointer_cast<const Fleet>(obj);
1844 } else if (obj->ObjectType() == OBJ_SHIP) {
1845 auto ship = std::dynamic_pointer_cast<const Ship>(obj);
1846 if (ship)
1847 fleet = Objects().get<Fleet>(ship->FleetID());
1848 }
1849 if (fleet) {
1850 int cur_id = fleet->SystemID();
1851 if (cur_id == INVALID_OBJECT_ID) // fleets do not grant detection when in a starlane
1852 continue;
1853 }
1854
1855 // record object's detection range for owner
1856 int object_owner_empire_id = obj->Owner();
1857 std::pair<double, double> object_pos(obj->X(), obj->Y());
1858 // store range in output map (if new for location or larger than any
1859 // previously-found range at this location)
1860 auto& retval_empire_pos_range = retval[object_owner_empire_id];
1861 auto retval_pos_it = retval_empire_pos_range.find(object_pos);
1862 if (retval_pos_it == retval_empire_pos_range.end())
1863 retval_empire_pos_range[object_pos] = object_detection_range;
1864 else
1865 retval_pos_it->second = std::max(retval_pos_it->second, object_detection_range);
1866 }
1867 return retval;
1868 }
1869
GetEmpiresDetectionStrengths(int empire_id=ALL_EMPIRES)1870 std::map<int, float> GetEmpiresDetectionStrengths(int empire_id = ALL_EMPIRES) {
1871 std::map<int, float> retval;
1872 if (empire_id == ALL_EMPIRES) {
1873 for (const auto& empire_entry : Empires()) {
1874 const Meter* meter = empire_entry.second->GetMeter("METER_DETECTION_STRENGTH");
1875 float strength = meter ? meter->Current() : 0.0f;
1876 retval[empire_entry.first] = strength;
1877 }
1878 } else {
1879 if (const Empire* empire = GetEmpire(empire_id))
1880 if (const Meter* meter = empire->GetMeter("METER_DETECTION_STRENGTH"))
1881 retval[empire_id] = meter->Current();
1882 }
1883 return retval;
1884 }
1885
1886 /** for each empire: for each position, what objects have low enough stealth
1887 * that the empire could detect them if an detector owned by the empire is in
1888 * range? */
1889 std::map<int, std::map<std::pair<double, double>, std::vector<int>>>
GetEmpiresPositionsPotentiallyDetectableObjects(const ObjectMap & objects,int empire_id=ALL_EMPIRES)1890 GetEmpiresPositionsPotentiallyDetectableObjects(const ObjectMap& objects, int empire_id = ALL_EMPIRES)
1891 {
1892 std::map<int, std::map<std::pair<double, double>, std::vector<int>>> retval;
1893
1894 auto empire_detection_strengths = GetEmpiresDetectionStrengths(empire_id);
1895
1896 // filter objects as detectors for this empire or detectable objects
1897 for (const auto& obj : objects.all())
1898 {
1899 const Meter* stealth_meter = obj->GetMeter(METER_STEALTH);
1900 if (!stealth_meter)
1901 continue;
1902 float object_stealth = stealth_meter->Current();
1903 std::pair<double, double> object_pos(obj->X(), obj->Y());
1904
1905 // for each empire being checked for, check if each object could be
1906 // detected by the empire if the empire has a detector in range.
1907 // being detectable by an empire requires the object to have
1908 // low enough stealth (0 or below the empire's detection strength)
1909 for (const auto& empire_entry : empire_detection_strengths) {
1910 if (object_stealth <= empire_entry.second || object_stealth == 0.0f || obj->OwnedBy(empire_entry.first))
1911 retval[empire_entry.first][object_pos].push_back(obj->ID());
1912 }
1913 }
1914 return retval;
1915 }
1916
1917 /** filters set of objects at locations by which of those locations are
1918 * within range of a set of detectors and ranges */
FilterObjectPositionsByDetectorPositionsAndRanges(const std::map<std::pair<double,double>,std::vector<int>> & object_positions,const std::map<std::pair<double,double>,float> & detector_position_ranges)1919 std::vector<int> FilterObjectPositionsByDetectorPositionsAndRanges(
1920 const std::map<std::pair<double, double>, std::vector<int>>& object_positions,
1921 const std::map<std::pair<double, double>, float>& detector_position_ranges)
1922 {
1923 std::vector<int> retval;
1924 // check each detector position and range against each object position
1925 for (const auto& object_position_entry : object_positions) {
1926 const auto& object_pos = object_position_entry.first;
1927 const auto& objects = object_position_entry.second;
1928 // search through detector positions until one is found in range
1929 for (const auto& detector_position_entry : detector_position_ranges) {
1930 // check range for this detector location for this detectables location
1931 float detector_range2 = detector_position_entry.second * detector_position_entry.second;
1932 const auto& detector_pos = detector_position_entry.first;
1933 double x_dist = detector_pos.first - object_pos.first;
1934 double y_dist = detector_pos.second - object_pos.second;
1935 double dist2 = x_dist*x_dist + y_dist*y_dist;
1936 if (dist2 > detector_range2)
1937 continue; // object out of range
1938 // add objects at position to return value
1939 std::copy(objects.begin(), objects.end(), std::back_inserter(retval));
1940 break; // done searching for a detector position in range
1941 }
1942 }
1943 return retval;
1944 }
1945
1946 /** removes ids of objects that the indicated empire knows have been
1947 * destroyed */
FilterObjectIDsByKnownDestruction(std::vector<int> & object_ids,int empire_id,const std::map<int,std::set<int>> & empire_known_destroyed_object_ids)1948 void FilterObjectIDsByKnownDestruction(std::vector<int>& object_ids, int empire_id,
1949 const std::map<int, std::set<int>>& empire_known_destroyed_object_ids)
1950 {
1951 if (empire_id == ALL_EMPIRES)
1952 return;
1953 for (auto it = object_ids.begin(); it != object_ids.end();) {
1954 int object_id = *it;
1955 auto obj_it = empire_known_destroyed_object_ids.find(object_id);
1956 if (obj_it == empire_known_destroyed_object_ids.end()) {
1957 ++it;
1958 continue;
1959 }
1960 const std::set<int>& empires_that_know = obj_it->second;
1961 if (!empires_that_know.count(empire_id)) {
1962 ++it;
1963 continue;
1964 }
1965 // remove object from vector...
1966 *it = object_ids.back();
1967 object_ids.pop_back();
1968 }
1969 }
1970
1971 /** sets visibility of field objects for empires based on input locations
1972 * and stealth of fields in supplied ObjectMap and input empire detection
1973 * ranges at locations. the rules for detection of fields are more
1974 * permissive than other object types, so a special function for them is
1975 * needed in addition to SetEmpireObjectVisibilitiesFromRanges(...) */
SetEmpireFieldVisibilitiesFromRanges(const std::map<int,std::map<std::pair<double,double>,float>> & empire_location_detection_ranges,const ObjectMap & objects)1976 void SetEmpireFieldVisibilitiesFromRanges(
1977 const std::map<int, std::map<std::pair<double, double>, float>>&
1978 empire_location_detection_ranges,
1979 const ObjectMap& objects)
1980 {
1981 Universe& universe = GetUniverse();
1982
1983 for (const auto& detecting_empire_entry : empire_location_detection_ranges) {
1984 int detecting_empire_id = detecting_empire_entry.first;
1985 double detection_strength = 0.0;
1986 const Empire* empire = GetEmpire(detecting_empire_id);
1987 if (!empire)
1988 continue;
1989 const Meter* meter = empire->GetMeter("METER_DETECTION_STRENGTH");
1990 if (!meter)
1991 continue;
1992 detection_strength = meter->Current();
1993
1994 // get empire's locations of detection ranges
1995 const auto& detector_position_ranges = detecting_empire_entry.second;
1996
1997 // for each field, try to find a detector position in range for this empire
1998 for (auto& field : objects.all<Field>()) {
1999 if (field->GetMeter(METER_STEALTH)->Current() > detection_strength)
2000 continue;
2001 double field_size = field->GetMeter(METER_SIZE)->Current();
2002 const std::pair<double, double> object_pos(field->X(), field->Y());
2003
2004 // search through detector positions until one is found in range
2005 for (const auto& detector_position_entry : detector_position_ranges) {
2006 // check range for this detector location, for field of this
2007 // size, against distance between field and detector
2008 float detector_range = detector_position_entry.second;
2009 const auto& detector_pos = detector_position_entry.first;
2010 double x_dist = detector_pos.first - object_pos.first;
2011 double y_dist = detector_pos.second - object_pos.second;
2012 double dist = std::sqrt(x_dist*x_dist + y_dist*y_dist);
2013 double effective_dist = dist - field_size;
2014 if (effective_dist > detector_range)
2015 continue; // object out of range
2016
2017 universe.SetEmpireObjectVisibility(detecting_empire_id, field->ID(),
2018 VIS_PARTIAL_VISIBILITY);
2019 }
2020 }
2021 }
2022 }
2023
2024 /** sets visibility of objects for empires based on input locations of
2025 * potentially detectable objects (if in range) and and input empire
2026 * detection ranges at locations. */
SetEmpireObjectVisibilitiesFromRanges(const std::map<int,std::map<std::pair<double,double>,float>> & empire_location_detection_ranges,const std::map<int,std::map<std::pair<double,double>,std::vector<int>>> & empire_location_potentially_detectable_objects)2027 void SetEmpireObjectVisibilitiesFromRanges(
2028 const std::map<int, std::map<std::pair<double, double>, float>>&
2029 empire_location_detection_ranges,
2030 const std::map<int, std::map<std::pair<double, double>, std::vector<int>>>&
2031 empire_location_potentially_detectable_objects)
2032 {
2033 Universe& universe = GetUniverse();
2034
2035 for (const auto& detecting_empire_entry : empire_location_detection_ranges) {
2036 int detecting_empire_id = detecting_empire_entry.first;
2037 // get empire's locations of detection ability
2038 const auto& detector_position_ranges = detecting_empire_entry.second;
2039 // for this empire, get objects it could potentially detect
2040 const auto empire_detectable_objects_it =
2041 empire_location_potentially_detectable_objects.find(detecting_empire_id);
2042 if (empire_detectable_objects_it == empire_location_potentially_detectable_objects.end())
2043 continue; // empire can't detect anything!
2044 const auto& detectable_position_objects = empire_detectable_objects_it->second;
2045 if (detectable_position_objects.empty())
2046 continue;
2047
2048 // filter potentially detectable objects by which are within range
2049 // of a detector
2050 std::vector<int> in_range_detectable_objects =
2051 FilterObjectPositionsByDetectorPositionsAndRanges(detectable_position_objects,
2052 detector_position_ranges);
2053 if (in_range_detectable_objects.empty())
2054 continue;
2055
2056 // set all in-range detectable objects as partially visible (unless
2057 // any are already full vis, in which case do nothing)
2058 for (int detected_object_id : in_range_detectable_objects) {
2059 universe.SetEmpireObjectVisibility(detecting_empire_id, detected_object_id,
2060 VIS_PARTIAL_VISIBILITY);
2061 }
2062 }
2063 }
2064
2065 /** sets visibility of objects that empires own for those objects */
SetEmpireOwnedObjectVisibilities()2066 void SetEmpireOwnedObjectVisibilities() {
2067 Universe& universe = GetUniverse();
2068 for (const auto& obj : universe.Objects().all()) {
2069 if (obj->Unowned())
2070 continue;
2071 universe.SetEmpireObjectVisibility(obj->Owner(), obj->ID(), VIS_FULL_VISIBILITY);
2072 }
2073 }
2074
2075 /** sets all objects visible to all empires */
SetAllObjectsVisibleToAllEmpires()2076 void SetAllObjectsVisibleToAllEmpires() {
2077 Universe& universe = GetUniverse();
2078 // set every object visible to all empires
2079 for (const auto& obj : universe.Objects().all()) {
2080 for (auto& empire_entry : Empires()) {
2081 if (empire_entry.second->Eliminated())
2082 continue;
2083 // objects
2084 universe.SetEmpireObjectVisibility(empire_entry.first, obj->ID(), VIS_FULL_VISIBILITY);
2085 // specials on objects
2086 for (const auto& special_entry : obj->Specials()) {
2087 universe.SetEmpireSpecialVisibility(empire_entry.first, obj->ID(), special_entry.first);
2088 }
2089 }
2090 }
2091 }
2092
2093 /** sets planets in system where an empire owns an object to be basically
2094 * visible, and those systems to be partially visible */
SetSameSystemPlanetsVisible(const ObjectMap & objects)2095 void SetSameSystemPlanetsVisible(const ObjectMap& objects) {
2096 Universe& universe = GetUniverse();
2097 // map from empire ID to ID of systems where those empires own at least one object
2098 std::map<int, std::set<int>> empires_systems_with_owned_objects;
2099 // get systems where empires have owned objects
2100 for (const auto& obj : objects.all()) {
2101 if (obj->Unowned() || obj->SystemID() == INVALID_OBJECT_ID)
2102 continue;
2103 empires_systems_with_owned_objects[obj->Owner()].insert(obj->SystemID());
2104 }
2105
2106 // set system visibility
2107 for (const auto& empire_entry : empires_systems_with_owned_objects) {
2108 int empire_id = empire_entry.first;
2109
2110 for (int system_id : empire_entry.second) {
2111 universe.SetEmpireObjectVisibility(empire_id, system_id, VIS_PARTIAL_VISIBILITY);
2112 }
2113 }
2114
2115 // get planets, check their locations...
2116 for (const auto& planet : objects.all<Planet>()) {
2117 int system_id = planet->SystemID();
2118 if (system_id == INVALID_OBJECT_ID)
2119 continue;
2120
2121 int planet_id = planet->ID();
2122 for (const auto& empire_entry : empires_systems_with_owned_objects) {
2123 int empire_id = empire_entry.first;
2124 const auto& empire_systems = empire_entry.second;
2125 if (!empire_systems.count(system_id))
2126 continue;
2127 // ensure planets are at least basicaly visible. does not
2128 // overwrite higher visibility levels
2129 universe.SetEmpireObjectVisibility(empire_id, planet_id, VIS_BASIC_VISIBILITY);
2130 }
2131 }
2132 }
2133
PropagateVisibilityToContainerObjects(const ObjectMap & objects,Universe::EmpireObjectVisibilityMap & empire_object_visibility)2134 void PropagateVisibilityToContainerObjects(const ObjectMap& objects,
2135 Universe::EmpireObjectVisibilityMap& empire_object_visibility)
2136 {
2137 // propagate visibility from contained to container objects
2138 for (const auto& container_obj : objects.all())
2139 {
2140 if (!container_obj)
2141 continue; // shouldn't be necessary, but I like to be safe...
2142
2143 // check if container object is a fleet, for special case later...
2144 bool container_fleet = container_obj->ObjectType() == OBJ_FLEET;
2145
2146 //DebugLogger() << "Container object " << container_obj->Name() << " (" << container_obj->ID() << ")";
2147
2148 // for each contained object within container
2149 for (int contained_obj_id : container_obj->ContainedObjectIDs()) {
2150 //DebugLogger() << " ... contained object (" << contained_obj_id << ")";
2151
2152 // for each empire with a visibility map
2153 for (auto& empire_entry : empire_object_visibility) {
2154 auto& vis_map = empire_entry.second;
2155
2156 //DebugLogger() << " ... ... empire id " << empire_entry.first;
2157
2158 // find current empire's visibility entry for current container object
2159 auto container_vis_it = vis_map.find(container_obj->ID());
2160 // if no entry yet stored for this object, default to not visible
2161 if (container_vis_it == vis_map.end()) {
2162 vis_map[container_obj->ID()] = VIS_NO_VISIBILITY;
2163
2164 // get iterator pointing at newly-created entry
2165 container_vis_it = vis_map.find(container_obj->ID());
2166 } else {
2167 // check whether having a contained object would change container's visibility
2168 if (container_fleet) {
2169 // special case for fleets: grant partial visibility if
2170 // a contained ship is seen with partial visibility or
2171 // higher visibilitly
2172 if (container_vis_it->second >= VIS_PARTIAL_VISIBILITY)
2173 continue;
2174 } else if (container_vis_it->second >= VIS_BASIC_VISIBILITY) {
2175 // general case: for non-fleets, having visible
2176 // contained object grants basic vis only. if
2177 // container already has this or better for the current
2178 // empire, don't need to propagate anything
2179 continue;
2180 }
2181 }
2182
2183
2184 // find contained object's entry in visibility map
2185 auto contained_vis_it = vis_map.find(contained_obj_id);
2186 if (contained_vis_it != vis_map.end()) {
2187 // get contained object's visibility for current empire
2188 Visibility contained_obj_vis = contained_vis_it->second;
2189
2190 // no need to propagate if contained object isn't visible to current empire
2191 if (contained_obj_vis <= VIS_NO_VISIBILITY)
2192 continue;
2193
2194 //DebugLogger() << " ... ... contained object vis: " << contained_obj_vis;
2195
2196 // contained object is at least basically visible.
2197 // container should be at least partially visible, but don't
2198 // want to decrease visibility of container if it is already
2199 // higher than partially visible
2200 if (container_vis_it->second < VIS_BASIC_VISIBILITY)
2201 container_vis_it->second = VIS_BASIC_VISIBILITY;
2202
2203 // special case for fleets: grant partial visibility if
2204 // visible contained object is partially or better visible
2205 // this way fleet ownership is known to players who can
2206 // see ships with partial or better visibility (and thus
2207 // know the owner of the ships and thus should know the
2208 // owners of the fleet)
2209 if (container_fleet && contained_obj_vis >= VIS_PARTIAL_VISIBILITY &&
2210 container_vis_it->second < VIS_PARTIAL_VISIBILITY)
2211 { container_vis_it->second = VIS_PARTIAL_VISIBILITY; }
2212 }
2213 } // end for empire visibility entries
2214 } // end for contained objects
2215 } // end for container objects
2216 }
2217
PropagateVisibilityToSystemsAlongStarlanes(const ObjectMap & objects,Universe::EmpireObjectVisibilityMap & empire_object_visibility)2218 void PropagateVisibilityToSystemsAlongStarlanes(
2219 const ObjectMap& objects, Universe::EmpireObjectVisibilityMap& empire_object_visibility)
2220 {
2221 for (auto& system : objects.all<System>()) {
2222 int system_id = system->ID();
2223
2224 // for each empire with a visibility map
2225 for (auto& empire_entry : empire_object_visibility) {
2226 auto& vis_map = empire_entry.second;
2227
2228 // find current system's visibility
2229 auto system_vis_it = vis_map.find(system_id);
2230 if (system_vis_it == vis_map.end())
2231 continue;
2232
2233 // skip systems that aren't at least partially visible; they can't propagate visibility along starlanes
2234 Visibility system_vis = system_vis_it->second;
2235 if (system_vis <= VIS_BASIC_VISIBILITY)
2236 continue;
2237
2238 // get all starlanes emanating from this system, and loop through them
2239 for (auto& lane : system->StarlanesWormholes()) {
2240 bool is_wormhole = lane.second;
2241 if (is_wormhole)
2242 continue;
2243
2244 // find entry for system on other end of starlane in visibility
2245 // map, and upgrade to basic visibility if not already at that
2246 // leve, so that starlanes will be visible if either system it
2247 // ends at is partially visible or better
2248 int lane_end_sys_id = lane.first;
2249 auto lane_end_vis_it = vis_map.find(lane_end_sys_id);
2250 if (lane_end_vis_it == vis_map.end())
2251 vis_map[lane_end_sys_id] = VIS_BASIC_VISIBILITY;
2252 else if (lane_end_vis_it->second < VIS_BASIC_VISIBILITY)
2253 lane_end_vis_it->second = VIS_BASIC_VISIBILITY;
2254 }
2255 }
2256 }
2257
2258 }
2259
SetTravelledStarlaneEndpointsVisible(const ObjectMap & objects,Universe::EmpireObjectVisibilityMap & empire_object_visibility)2260 void SetTravelledStarlaneEndpointsVisible(const ObjectMap& objects,
2261 Universe::EmpireObjectVisibilityMap& empire_object_visibility)
2262 {
2263 // ensure systems on either side of a starlane along which a fleet is
2264 // moving are at least basically visible, so that the starlane itself can /
2265 // will be visible
2266 for (auto& obj : objects.find(MovingFleetVisitor())) {
2267 if (obj->Unowned() || obj->SystemID() == INVALID_OBJECT_ID || obj->ObjectType() != OBJ_FLEET)
2268 continue;
2269 auto fleet = std::dynamic_pointer_cast<const Fleet>(obj);
2270 if (!fleet)
2271 continue;
2272
2273 int prev = fleet->PreviousSystemID();
2274 int next = fleet->NextSystemID();
2275
2276 // ensure fleet's owner has at least basic visibility of the next
2277 // and previous systems on the fleet's path
2278 auto& vis_map = empire_object_visibility[fleet->Owner()];
2279
2280 auto system_vis_it = vis_map.find(prev);
2281 if (system_vis_it == vis_map.end()) {
2282 vis_map[prev] = VIS_BASIC_VISIBILITY;
2283 } else {
2284 if (system_vis_it->second < VIS_BASIC_VISIBILITY)
2285 system_vis_it->second = VIS_BASIC_VISIBILITY;
2286 }
2287
2288 system_vis_it = vis_map.find(next);
2289 if (system_vis_it == vis_map.end()) {
2290 vis_map[next] = VIS_BASIC_VISIBILITY;
2291 } else {
2292 if (system_vis_it->second < VIS_BASIC_VISIBILITY)
2293 system_vis_it->second = VIS_BASIC_VISIBILITY;
2294 }
2295 }
2296 }
2297
SetEmpireSpecialVisibilities(ObjectMap & objects,Universe::EmpireObjectVisibilityMap & empire_object_visibility,Universe::EmpireObjectSpecialsMap & empire_object_visible_specials)2298 void SetEmpireSpecialVisibilities(ObjectMap& objects,
2299 Universe::EmpireObjectVisibilityMap& empire_object_visibility,
2300 Universe::EmpireObjectSpecialsMap& empire_object_visible_specials)
2301 {
2302 // after setting object visibility, similarly set visibility of objects'
2303 // specials for each empire
2304 for (auto& empire_entry : Empires()) {
2305 int empire_id = empire_entry.first;
2306 auto& obj_vis_map = empire_object_visibility[empire_id];
2307 auto& obj_specials_map = empire_object_visible_specials[empire_id];
2308
2309 const Empire* empire = empire_entry.second;
2310 const Meter* detection_meter = empire->GetMeter("METER_DETECTION_STRENGTH");
2311 if (!detection_meter)
2312 continue;
2313 float detection_strength = detection_meter->Current();
2314
2315 // every object empire has visibility of might have specials
2316 for (auto& obj_entry : obj_vis_map) {
2317 if (obj_entry.second <= VIS_NO_VISIBILITY)
2318 continue;
2319
2320 int object_id = obj_entry.first;
2321 auto obj = objects.get(object_id);
2322 if (!obj)
2323 continue;
2324
2325 if (obj->Specials().empty())
2326 continue;
2327
2328 auto& visible_specials = obj_specials_map[object_id];
2329
2330 // check all object's specials.
2331 for (const auto& special_entry : obj->Specials()) {
2332 const Special* special = GetSpecial(special_entry.first);
2333 if (!special)
2334 continue;
2335
2336 float stealth = 0.0f;
2337 const auto special_stealth = special->Stealth();
2338 if (special_stealth)
2339 stealth = special_stealth->Eval(ScriptingContext(obj, objects));
2340
2341 // if special is 0 stealth, or has stealth less than empire's detection strength, mark as visible
2342 if (stealth <= 0.0f || stealth <= detection_strength) {
2343 visible_specials.insert(special_entry.first);
2344 //DebugLogger() << "Special " << special_entry.first << " on " << obj->Name() << " is visible to empire " << empire_id;
2345 }
2346 }
2347 }
2348 }
2349 }
2350
ShareVisbilitiesBetweenAllies(Universe::EmpireObjectVisibilityMap & empire_object_visibility,Universe::EmpireObjectSpecialsMap & empire_object_visible_specials)2351 void ShareVisbilitiesBetweenAllies(Universe::EmpireObjectVisibilityMap& empire_object_visibility,
2352 Universe::EmpireObjectSpecialsMap& empire_object_visible_specials)
2353 {
2354 // make copy of input vis map, iterate over that, not the output as
2355 // iterating over the output while modifying it would result in
2356 // second-order visibility sharing (but only through allies with lower
2357 // empire id)
2358 auto input_eov_copy = empire_object_visibility;
2359 auto input_eovs_copy = empire_object_visible_specials;
2360 Universe& universe = GetUniverse();
2361
2362 for (auto& empire_entry : Empires()) {
2363 int empire_id = empire_entry.first;
2364 // output maps for this empire
2365 auto& obj_vis_map = empire_object_visibility[empire_id];
2366 auto& obj_specials_map = empire_object_visible_specials[empire_id];
2367
2368 for (auto allied_empire_id : Empires().GetEmpireIDsWithDiplomaticStatusWithEmpire(empire_id, DIPLO_ALLIED)) {
2369 if (empire_id == allied_empire_id) {
2370 ErrorLogger() << "ShareVisbilitiesBetweenAllies : Empire apparent allied with itself!";
2371 continue;
2372 }
2373
2374 // input maps for this ally empire
2375 auto& allied_obj_vis_map = input_eov_copy[allied_empire_id];
2376 auto& allied_obj_specials_map = input_eovs_copy[allied_empire_id];
2377
2378 // add allied visibilities to outer-loop empire visibilities
2379 // whenever the ally has better visibility of an object
2380 // (will do the reverse in another loop iteration)
2381 for (auto const& allied_obj_id_vis_pair : allied_obj_vis_map) {
2382 int obj_id = allied_obj_id_vis_pair.first;
2383 Visibility allied_vis = allied_obj_id_vis_pair.second;
2384 auto it = obj_vis_map.find(obj_id);
2385 if (it == obj_vis_map.end() || it->second < allied_vis) {
2386 obj_vis_map[obj_id] = allied_vis;
2387 if (allied_vis < VIS_PARTIAL_VISIBILITY)
2388 continue;
2389 if (auto ship = Objects().get<Ship>(obj_id))
2390 universe.SetEmpireKnowledgeOfShipDesign(ship->DesignID(), empire_id);
2391 }
2392 }
2393
2394 // add allied visibilities of specials to outer-loop empire
2395 // visibilities as well
2396 for (const auto& allied_obj_special_vis_pair : allied_obj_specials_map) {
2397 int obj_id = allied_obj_special_vis_pair.first;
2398 const auto& specials = allied_obj_special_vis_pair.second;
2399 obj_specials_map[obj_id].insert(specials.begin(), specials.end());
2400 }
2401 }
2402 }
2403 }
2404 }
2405
UpdateEmpireObjectVisibilities()2406 void Universe::UpdateEmpireObjectVisibilities() {
2407 // ensure Universe knows empires have knowledge of designs the empire is specifically remembering
2408 for (auto& empire_entry : Empires()) {
2409 int empire_id = empire_entry.first;
2410 const Empire* empire = empire_entry.second;
2411 if (empire->Eliminated()) {
2412 m_empire_known_ship_design_ids.erase(empire_id);
2413 } else {
2414 for (int design_id : empire->ShipDesigns())
2415 m_empire_known_ship_design_ids[empire_id].insert(design_id);
2416 }
2417 }
2418
2419 m_empire_object_visibility.clear();
2420 m_empire_object_visible_specials.clear();
2421
2422 if (m_all_objects_visible) {
2423 SetAllObjectsVisibleToAllEmpires();
2424 return;
2425 }
2426
2427
2428 SetEmpireOwnedObjectVisibilities();
2429
2430 auto empire_position_detection_ranges = GetEmpiresPositionDetectionRanges();
2431
2432 auto empire_position_potentially_detectable_objects =
2433 GetEmpiresPositionsPotentiallyDetectableObjects(m_objects);
2434
2435 SetEmpireObjectVisibilitiesFromRanges(empire_position_detection_ranges,
2436 empire_position_potentially_detectable_objects);
2437 SetEmpireFieldVisibilitiesFromRanges(empire_position_detection_ranges, m_objects);
2438
2439 SetSameSystemPlanetsVisible(m_objects);
2440
2441 ApplyEffectDerivedVisibilities();
2442
2443 PropagateVisibilityToContainerObjects(m_objects, m_empire_object_visibility);
2444
2445 PropagateVisibilityToSystemsAlongStarlanes(m_objects, m_empire_object_visibility);
2446
2447 SetTravelledStarlaneEndpointsVisible(m_objects, m_empire_object_visibility);
2448
2449 SetEmpireSpecialVisibilities(m_objects, m_empire_object_visibility, m_empire_object_visible_specials);
2450
2451 ShareVisbilitiesBetweenAllies(m_empire_object_visibility, m_empire_object_visible_specials);
2452 }
2453
UpdateEmpireLatestKnownObjectsAndVisibilityTurns()2454 void Universe::UpdateEmpireLatestKnownObjectsAndVisibilityTurns() {
2455 //DebugLogger() << "Universe::UpdateEmpireLatestKnownObjectsAndVisibilityTurns()";
2456
2457 // assumes m_empire_object_visibility has been updated
2458
2459 // for each object in universe
2460 // for each empire that can see object this turn
2461 // update empire's information about object, based on visibility
2462 // update empire's visbilility turn history
2463
2464 int current_turn = CurrentTurn();
2465 if (current_turn == INVALID_GAME_TURN)
2466 return;
2467
2468 // for each object in universe
2469 for (const auto& full_object : m_objects.all()) {
2470 if (!full_object) {
2471 ErrorLogger() << "UpdateEmpireLatestKnownObjectsAndVisibilityTurns found null object in m_objects";
2472 continue;
2473 }
2474 int object_id = full_object->ID();
2475
2476 // for each empire with a visibility map
2477 for (auto& empire_entry : m_empire_object_visibility) {
2478 // can empire see object?
2479 const auto& vis_map = empire_entry.second; // stores level of visibility empire has for each object it can detect this turn
2480 auto vis_it = vis_map.find(object_id);
2481 if (vis_it == vis_map.end())
2482 continue; // empire can't see current object, so move to next empire
2483 const Visibility vis = vis_it->second;
2484 if (vis <= VIS_NO_VISIBILITY)
2485 continue; // empire can't see current object, so move to next empire
2486
2487 // empire can see object. need to update empire's latest known
2488 // information about object, and historical turns on which object
2489 // was seen at various visibility levels.
2490
2491 int empire_id = empire_entry.first;
2492
2493 ObjectMap& known_object_map = m_empire_latest_known_objects[empire_id]; // creates empty map if none yet present
2494 ObjectVisibilityTurnMap& object_vis_turn_map = m_empire_object_visibility_turns[empire_id]; // creates empty map if none yet present
2495 VisibilityTurnMap& vis_turn_map = object_vis_turn_map[object_id]; // creates empty map if none yet present
2496
2497
2498 // update empire's latest known data about object, based on current visibility and historical visibility and knowledge of object
2499
2500 // is there already last known version of an UniverseObject stored for this empire?
2501 if (auto known_obj = known_object_map.get(object_id)) {
2502 known_obj->Copy(full_object, empire_id); // already a stored version of this object for this empire. update it, limited by visibility this empire has for this object this turn
2503 } else {
2504 if (auto new_obj = std::shared_ptr<UniverseObject>(full_object->Clone(empire_id))) // no previously-recorded version of this object for this empire. create a new one, copying only the information limtied by visibility, leaving the rest as default values
2505 known_object_map.insert(new_obj);
2506 }
2507
2508 //DebugLogger() << "Empire " << empire_id << " can see object " << object_id << " with vis level " << vis;
2509
2510 // update empire's visibility turn history for current vis, and lesser vis levels
2511 if (vis >= VIS_BASIC_VISIBILITY) {
2512 vis_turn_map[VIS_BASIC_VISIBILITY] = current_turn;
2513 if (vis >= VIS_PARTIAL_VISIBILITY) {
2514 vis_turn_map[VIS_PARTIAL_VISIBILITY] = current_turn;
2515 if (vis >= VIS_FULL_VISIBILITY) {
2516 vis_turn_map[VIS_FULL_VISIBILITY] = current_turn;
2517 }
2518 }
2519 //DebugLogger() << " ... Setting empire " << empire_id << " object " << full_object->Name() << " (" << object_id << ") vis " << vis << " (and higher) turn to " << current_turn;
2520 } else {
2521 ErrorLogger() << "Universe::UpdateEmpireLatestKnownObjectsAndVisibilityTurns() found invalid visibility for object with id " << object_id << " by empire with id " << empire_id;
2522 continue;
2523 }
2524 }
2525 }
2526 }
2527
UpdateEmpireStaleObjectKnowledge()2528 void Universe::UpdateEmpireStaleObjectKnowledge() {
2529 // if any objects in the latest known objects for an empire are not
2530 // currently visible, but that empire has detectors in range of the objects'
2531 // latest known location and the objects' latest known stealth is low enough to be
2532 // detectable by that empire, then the latest known state of the objects
2533 // (including stealth and position) appears to be stale / out of date.
2534
2535 const auto empire_location_detection_ranges = GetEmpiresPositionDetectionRanges();
2536
2537 for (const auto& empire_entry : m_empire_latest_known_objects) {
2538 int empire_id = empire_entry.first;
2539 const ObjectMap& latest_known_objects = empire_entry.second;
2540 const ObjectVisibilityMap& vis_map = m_empire_object_visibility[empire_id];
2541 std::set<int>& stale_set = m_empire_stale_knowledge_object_ids[empire_id];
2542 const std::set<int>& destroyed_set = m_empire_known_destroyed_object_ids[empire_id];
2543
2544 // remove stale marking for any known destroyed or currently visible objects
2545 for (auto stale_it = stale_set.begin(); stale_it != stale_set.end();) {
2546 int object_id = *stale_it;
2547 if (vis_map.count(object_id) || destroyed_set.count(object_id))
2548 stale_it = stale_set.erase(stale_it);
2549 else
2550 ++stale_it;
2551 }
2552
2553
2554 // get empire latest known objects that are potentially detectable
2555 auto empires_latest_known_objects_that_should_be_detectable =
2556 GetEmpiresPositionsPotentiallyDetectableObjects(latest_known_objects, empire_id);
2557 auto& empire_latest_known_should_be_still_detectable_objects =
2558 empires_latest_known_objects_that_should_be_detectable[empire_id];
2559
2560
2561 // get empire detection ranges
2562 auto empire_detectors_it = empire_location_detection_ranges.find(empire_id);
2563 if (empire_detectors_it == empire_location_detection_ranges.end())
2564 continue;
2565 const auto& empire_detector_positions_ranges = empire_detectors_it->second;
2566
2567
2568 // filter should-be-still-detectable objects by whether they are
2569 // in range of a detector
2570 std::vector<int> should_still_be_detectable_latest_known_objects =
2571 FilterObjectPositionsByDetectorPositionsAndRanges(
2572 empire_latest_known_should_be_still_detectable_objects,
2573 empire_detector_positions_ranges);
2574
2575
2576 // filter to exclude objects that are known to have been destroyed, as
2577 // their last state is not stale information
2578 FilterObjectIDsByKnownDestruction(should_still_be_detectable_latest_known_objects,
2579 empire_id, m_empire_known_destroyed_object_ids);
2580
2581
2582 // any objects that pass filters but aren't actually still visible
2583 // represent out-of-date info in empire's latest known objects. these
2584 // entries need to be removed / flagged to indicate this
2585 for (int object_id : should_still_be_detectable_latest_known_objects) {
2586 auto vis_it = vis_map.find(object_id);
2587 if (vis_it == vis_map.end() || vis_it->second < VIS_BASIC_VISIBILITY) {
2588 // object not visible even though the latest known info about it
2589 // for this empire suggests it should be. info is stale.
2590 stale_set.insert(object_id);
2591 }
2592 }
2593
2594
2595 // fleets that are not visible and that contain no ships or only stale ships are stale
2596 for (const auto& fleet : latest_known_objects.all<Fleet>())
2597 {
2598 if (fleet->GetVisibility(empire_id) >= VIS_BASIC_VISIBILITY)
2599 continue;
2600
2601 // destroyed? not stale
2602 if (destroyed_set.count(fleet->ID())) {
2603 stale_set.insert(fleet->ID());
2604 continue;
2605 }
2606
2607 // no ships? -> stale
2608 if (fleet->Empty()) {
2609 stale_set.insert(fleet->ID());
2610 continue;
2611 }
2612
2613 bool fleet_stale = true;
2614 // check each ship. if any are visible or not visible but not stale,
2615 // fleet is not stale
2616 for (const auto& ship : latest_known_objects.find<Ship>(fleet->ShipIDs())) {
2617 // if ship doesn't think it's in this fleet, doesn't count.
2618 if (!ship || ship->FleetID() != fleet->ID())
2619 continue;
2620
2621 // if ship is destroyed, doesn't count
2622 if (destroyed_set.count(ship->ID()))
2623 continue;
2624
2625 // is contained ship visible? If so, fleet is not stale.
2626 auto vis_it = vis_map.find(ship->ID());
2627 if (vis_it != vis_map.end() && vis_it->second > VIS_NO_VISIBILITY) {
2628 fleet_stale = false;
2629 break;
2630 }
2631
2632 // is contained ship not visible and not stale? if so, fleet is not stale
2633 if (!stale_set.count(ship->ID())) {
2634 fleet_stale = false;
2635 break;
2636 }
2637 }
2638 if (fleet_stale)
2639 stale_set.insert(fleet->ID());
2640 }
2641
2642 //for (int stale_id : stale_set) {
2643 // auto obj = latest_known_objects.Object(stale_id);
2644 // DebugLogger() << "Object " << stale_id << " : " << (obj ? obj->Name() : "(unknown)") << " is stale for empire " << empire_id ;
2645 //}
2646 }
2647 }
2648
SetEmpireKnowledgeOfDestroyedObject(int object_id,int empire_id)2649 void Universe::SetEmpireKnowledgeOfDestroyedObject(int object_id, int empire_id) {
2650 if (object_id == INVALID_OBJECT_ID) {
2651 ErrorLogger() << "SetEmpireKnowledgeOfDestroyedObject called with INVALID_OBJECT_ID";
2652 return;
2653 }
2654 if (!GetEmpire(empire_id)) {
2655 ErrorLogger() << "SetEmpireKnowledgeOfDestroyedObject called for invalid empire id: " << empire_id;
2656 return;
2657 }
2658 m_empire_known_destroyed_object_ids[empire_id].insert(object_id);
2659 }
2660
SetEmpireKnowledgeOfShipDesign(int ship_design_id,int empire_id)2661 void Universe::SetEmpireKnowledgeOfShipDesign(int ship_design_id, int empire_id) {
2662 if (ship_design_id == INVALID_DESIGN_ID) {
2663 ErrorLogger() << "SetEmpireKnowledgeOfShipDesign called with INVALID_DESIGN_ID";
2664 return;
2665 }
2666 if (empire_id == ALL_EMPIRES)
2667 return;
2668 if (!GetEmpire(empire_id))
2669 ErrorLogger() << "SetEmpireKnowledgeOfShipDesign called for invalid empire id: " << empire_id;
2670
2671 m_empire_known_ship_design_ids[empire_id].insert(ship_design_id);
2672 }
2673
Destroy(int object_id,bool update_destroyed_object_knowers)2674 void Universe::Destroy(int object_id, bool update_destroyed_object_knowers/* = true*/) {
2675 // remove object from any containing UniverseObject
2676 auto obj = m_objects.get(object_id);
2677 if (!obj) {
2678 ErrorLogger() << "Universe::Destroy called for nonexistant object with id: " << object_id;
2679 return;
2680 }
2681
2682 m_destroyed_object_ids.insert(object_id);
2683
2684 if (update_destroyed_object_knowers) {
2685 // record empires that know this object has been destroyed
2686 for (auto& empire_entry : Empires()) {
2687 int empire_id = empire_entry.first;
2688 if (obj->GetVisibility(empire_id) >= VIS_BASIC_VISIBILITY) {
2689 SetEmpireKnowledgeOfDestroyedObject(object_id, empire_id);
2690 // TODO: Update m_empire_latest_known_objects somehow?
2691 }
2692 }
2693 }
2694
2695 // signal that an object has been deleted
2696 UniverseObjectDeleteSignal(obj);
2697 m_objects.erase(object_id);
2698 }
2699
RecursiveDestroy(int object_id)2700 std::set<int> Universe::RecursiveDestroy(int object_id) {
2701 std::set<int> retval;
2702
2703 auto obj = m_objects.get(object_id);
2704 if (!obj) {
2705 DebugLogger() << "Universe::RecursiveDestroy asked to destroy nonexistant object with id " << object_id;
2706 return retval;
2707 }
2708
2709 auto system = m_objects.get<System>(obj->SystemID());
2710
2711 if (auto ship = std::dynamic_pointer_cast<Ship>(obj)) {
2712 // if a ship is being deleted, and it is the last ship in its fleet, then the empty fleet should also be deleted
2713 auto fleet = m_objects.get<Fleet>(ship->FleetID());
2714 if (fleet) {
2715 fleet->RemoveShips({ship->ID()});
2716 if (fleet->Empty()) {
2717 if (system)
2718 system->Remove(fleet->ID());
2719 Destroy(fleet->ID());
2720 retval.insert(fleet->ID());
2721 }
2722 }
2723 if (system)
2724 system->Remove(object_id);
2725 Destroy(object_id);
2726 retval.insert(object_id);
2727
2728 } else if (auto obj_fleet = std::dynamic_pointer_cast<Fleet>(obj)) {
2729 for (int ship_id : obj_fleet->ShipIDs()) {
2730 if (system)
2731 system->Remove(ship_id);
2732 Destroy(ship_id);
2733 retval.insert(ship_id);
2734 }
2735 if (system)
2736 system->Remove(object_id);
2737 Destroy(object_id);
2738 retval.insert(object_id);
2739
2740 } else if (auto obj_planet = std::dynamic_pointer_cast<Planet>(obj)) {
2741 for (int building_id : obj_planet->BuildingIDs()) {
2742 if (system)
2743 system->Remove(building_id);
2744 Destroy(building_id);
2745 retval.insert(building_id);
2746 }
2747 if (system)
2748 system->Remove(object_id);
2749 Destroy(object_id);
2750 retval.insert(object_id);
2751
2752 } else if (auto obj_system = std::dynamic_pointer_cast<System>(obj)) {
2753 // destroy all objects in system
2754 for (int system_id : obj_system->ObjectIDs()) {
2755 Destroy(system_id);
2756 retval.insert(system_id);
2757 }
2758
2759 // remove any starlane connections to this system
2760 int this_sys_id = obj_system->ID();
2761 for (auto& sys : m_objects.all<System>()) {
2762 sys->RemoveStarlane(this_sys_id);
2763 }
2764
2765 // remove fleets / ships moving along destroyed starlane
2766 std::vector<std::shared_ptr<Fleet>> fleets_to_destroy;
2767 for (auto& fleet : m_objects.all<Fleet>()) {
2768 if (fleet->SystemID() == INVALID_OBJECT_ID && (
2769 fleet->NextSystemID() == this_sys_id ||
2770 fleet->PreviousSystemID() == this_sys_id))
2771 { fleets_to_destroy.push_back(fleet); }
2772 }
2773 for (auto& fleet : fleets_to_destroy)
2774 RecursiveDestroy(fleet->ID());
2775
2776 // then destroy system itself
2777 Destroy(object_id);
2778 retval.insert(object_id);
2779 // don't need to bother with removing things from system, fleets, or
2780 // ships, since everything in system is being destroyed
2781
2782 } else if (auto building = std::dynamic_pointer_cast<Building>(obj)) {
2783 auto planet = m_objects.get<Planet>(building->PlanetID());
2784 if (planet)
2785 planet->RemoveBuilding(object_id);
2786 if (system)
2787 system->Remove(object_id);
2788 Destroy(object_id);
2789 retval.insert(object_id);
2790
2791 } else if (obj->ObjectType() == OBJ_FIELD) {
2792 if (system)
2793 system->Remove(object_id);
2794 Destroy(object_id);
2795 retval.insert(object_id);
2796 }
2797 // else ??? object is of some type unknown as of this writing.
2798 return retval;
2799 }
2800
Delete(int object_id)2801 bool Universe::Delete(int object_id) {
2802 DebugLogger() << "Universe::Delete with ID: " << object_id;
2803 // find object amongst existing objects and delete directly, without storing
2804 // any info about the previous object (as is done for destroying an object)
2805 auto obj = m_objects.get(object_id);
2806 if (!obj) {
2807 ErrorLogger() << "Tried to delete a nonexistant object with id: " << object_id;
2808 return false;
2809 }
2810
2811 // move object to invalid position, thereby removing it from anything that
2812 // contained it and propagating associated signals
2813 obj->MoveTo(UniverseObject::INVALID_POSITION, UniverseObject::INVALID_POSITION);
2814 // remove from existing objects set
2815 m_objects.erase(object_id);
2816
2817 // TODO: Should this also remove the object from the latest known objects
2818 // and known destroyed objects for each empire?
2819
2820 return true;
2821 }
2822
EffectDestroy(int object_id,int source_object_id)2823 void Universe::EffectDestroy(int object_id, int source_object_id) {
2824 if (m_marked_destroyed.count(object_id))
2825 return;
2826 m_marked_destroyed[object_id].insert(source_object_id);
2827 }
2828
InitializeSystemGraph(int for_empire_id)2829 void Universe::InitializeSystemGraph(int for_empire_id) {
2830 std::vector<int> system_ids;
2831 for (const auto& system : ::EmpireKnownObjects(for_empire_id).all<System>()) {
2832 system_ids.push_back(system->ID());
2833 }
2834
2835 m_pathfinder->InitializeSystemGraph(system_ids, for_empire_id);
2836 }
2837
2838 //TODO Universe::UpdateEmpireVisibilityFilteredSystemGraphs is never
2839 //used. Decide if the functionality permanently belongs in Pathfinder
UpdateEmpireVisibilityFilteredSystemGraphs(int empire_id)2840 void Universe::UpdateEmpireVisibilityFilteredSystemGraphs(int empire_id) {
2841 m_pathfinder->UpdateEmpireVisibilityFilteredSystemGraphs(empire_id);
2842 }
2843
EncodingEmpire()2844 int& Universe::EncodingEmpire()
2845 { return m_encoding_empire; }
2846
UniverseWidth() const2847 double Universe::UniverseWidth() const
2848 { return m_universe_width; }
2849
UniverseObjectSignalsInhibited()2850 const bool& Universe::UniverseObjectSignalsInhibited()
2851 { return m_inhibit_universe_object_signals; }
2852
InhibitUniverseObjectSignals(bool inhibit)2853 void Universe::InhibitUniverseObjectSignals(bool inhibit)
2854 { m_inhibit_universe_object_signals = inhibit; }
2855
UpdateStatRecords()2856 void Universe::UpdateStatRecords() {
2857 int current_turn = CurrentTurn();
2858 if (current_turn == INVALID_GAME_TURN)
2859 return;
2860 if (current_turn == 0)
2861 m_stat_records.clear();
2862
2863 std::map<int, std::shared_ptr<const UniverseObject>> empire_sources;
2864 for (const auto& empire_entry : Empires()) {
2865 if (empire_entry.second->Eliminated())
2866 continue;
2867 auto source = empire_entry.second->Source();
2868 if (!source) {
2869 ErrorLogger() << "Universe::UpdateStatRecords() unable to find source for empire, id = "
2870 << empire_entry.second->EmpireID();
2871 continue;
2872 }
2873 empire_sources[empire_entry.first] = source;
2874 }
2875
2876 // process each stat
2877 for (const auto& stat_entry : EmpireStats()) {
2878 const std::string& stat_name = stat_entry.first;
2879
2880 const auto& value_ref = stat_entry.second;
2881 if (!value_ref)
2882 continue;
2883
2884 auto& stat_records = m_stat_records[stat_name];
2885
2886 // calculate stat for each empire, store in records for current turn
2887 for (const auto& entry : empire_sources) {
2888 int empire_id = entry.first;
2889
2890 if (value_ref->SourceInvariant()) {
2891 stat_records[empire_id][current_turn] = value_ref->Eval();
2892 } else if (entry.second) {
2893 stat_records[empire_id][current_turn] = value_ref->Eval(ScriptingContext(entry.second, m_objects));
2894 }
2895 }
2896 }
2897 }
2898
GetShipDesignsToSerialize(ShipDesignMap & designs_to_serialize,int encoding_empire) const2899 void Universe::GetShipDesignsToSerialize(ShipDesignMap& designs_to_serialize,
2900 int encoding_empire) const
2901 {
2902 if (encoding_empire == ALL_EMPIRES) {
2903 designs_to_serialize = m_ship_designs;
2904 } else {
2905 designs_to_serialize.clear();
2906
2907 // add generic monster ship designs so they always appear in players' pedias
2908 for (const auto& ship_design_entry : m_ship_designs) {
2909 ShipDesign* design = ship_design_entry.second;
2910 if (design->IsMonster() && design->DesignedByEmpire() == ALL_EMPIRES)
2911 designs_to_serialize[design->ID()] = design;
2912 }
2913
2914 // get empire's known ship designs
2915 auto it = m_empire_known_ship_design_ids.find(encoding_empire);
2916 if (it == m_empire_known_ship_design_ids.end())
2917 return; // no known designs to serialize
2918
2919 const std::set<int>& empire_designs = it->second;
2920
2921 // add all ship designs of ships this empire knows about
2922 for (int design_id : empire_designs) {
2923 auto universe_design_it = m_ship_designs.find(design_id);
2924 if (universe_design_it != m_ship_designs.end())
2925 designs_to_serialize[design_id] = universe_design_it->second;
2926 else
2927 ErrorLogger() << "Universe::GetShipDesignsToSerialize empire " << encoding_empire << " should know about design with id " << design_id << " but no such design exists in the Universe!";
2928 }
2929 }
2930 }
2931
GetObjectsToSerialize(ObjectMap & objects,int encoding_empire) const2932 void Universe::GetObjectsToSerialize(ObjectMap& objects, int encoding_empire) const {
2933 if (&objects == &m_objects)
2934 return;
2935
2936 objects.clear();
2937
2938 if (encoding_empire == ALL_EMPIRES) {
2939 // if encoding for all empires, copy true full universe state, and use the
2940 // streamlined option
2941 objects.CopyForSerialize(m_objects);
2942
2943 } else if (!ENABLE_VISIBILITY_EMPIRE_MEMORY) {
2944 // if encoding without memory, copy all info visible to specified empire
2945 objects.Copy(m_objects, encoding_empire);
2946
2947 } else {
2948 // if encoding for a specific empire with memory...
2949
2950 // find indicated empire's knowledge about objects, current and previous
2951 auto it = m_empire_latest_known_objects.find(encoding_empire);
2952 if (it == m_empire_latest_known_objects.end())
2953 return; // empire has no object knowledge, so there is nothing to send
2954
2955 //the empire_latest_known_objects are already processed for visibility, so can be copied streamlined
2956 objects.CopyForSerialize(it->second);
2957
2958 auto destroyed_ids_it = m_empire_known_destroyed_object_ids.find(encoding_empire);
2959 bool map_avail = (destroyed_ids_it != m_empire_known_destroyed_object_ids.end());
2960 const auto& destroyed_object_ids = map_avail ?
2961 destroyed_ids_it->second : std::set<int>();
2962
2963 objects.AuditContainment(destroyed_object_ids);
2964 }
2965 }
2966
GetDestroyedObjectsToSerialize(std::set<int> & destroyed_object_ids,int encoding_empire) const2967 void Universe::GetDestroyedObjectsToSerialize(std::set<int>& destroyed_object_ids,
2968 int encoding_empire) const
2969 {
2970 if (&destroyed_object_ids == &m_destroyed_object_ids)
2971 return;
2972
2973 if (encoding_empire == ALL_EMPIRES) {
2974 // all destroyed objects
2975 destroyed_object_ids = m_destroyed_object_ids;
2976 } else {
2977 destroyed_object_ids.clear();
2978 // get empire's known destroyed objects
2979 auto it = m_empire_known_destroyed_object_ids.find(encoding_empire);
2980 if (it != m_empire_known_destroyed_object_ids.end())
2981 destroyed_object_ids = it->second;
2982 }
2983 }
2984
GetEmpireKnownObjectsToSerialize(EmpireObjectMap & empire_latest_known_objects,int encoding_empire) const2985 void Universe::GetEmpireKnownObjectsToSerialize(EmpireObjectMap& empire_latest_known_objects,
2986 int encoding_empire) const
2987 {
2988 if (&empire_latest_known_objects == &m_empire_latest_known_objects)
2989 return;
2990
2991 DebugLogger() << "GetEmpireKnownObjectsToSerialize";
2992
2993 for (auto& entry : empire_latest_known_objects)
2994 entry.second.clear();
2995
2996 empire_latest_known_objects.clear();
2997
2998 if (!ENABLE_VISIBILITY_EMPIRE_MEMORY)
2999 return;
3000
3001 if (encoding_empire == ALL_EMPIRES) {
3002 // copy all ObjectMaps' contents
3003 for (const auto& entry : m_empire_latest_known_objects) {
3004 int empire_id = entry.first;
3005 const ObjectMap& map = entry.second;
3006 //the maps in m_empire_latest_known_objects are already processed for visibility, so can be copied fully
3007 empire_latest_known_objects[empire_id].CopyForSerialize(map);
3008 }
3009 return;
3010 }
3011 }
3012
GetEmpireObjectVisibilityMap(EmpireObjectVisibilityMap & empire_object_visibility,int encoding_empire) const3013 void Universe::GetEmpireObjectVisibilityMap(EmpireObjectVisibilityMap& empire_object_visibility,
3014 int encoding_empire) const
3015 {
3016 if (encoding_empire == ALL_EMPIRES) {
3017 empire_object_visibility = m_empire_object_visibility;
3018 return;
3019 }
3020
3021 // include just requested empire's visibility for each object it has better
3022 // than no visibility of. TODO: include what requested empire knows about
3023 // other empires' visibilites of objects
3024 empire_object_visibility.clear();
3025 for (const auto& object : m_objects.all()) {
3026 Visibility vis = GetObjectVisibilityByEmpire(object->ID(), encoding_empire);
3027 if (vis > VIS_NO_VISIBILITY)
3028 empire_object_visibility[encoding_empire][object->ID()] = vis;
3029 }
3030 }
3031
GetEmpireObjectVisibilityTurnMap(EmpireObjectVisibilityTurnMap & empire_object_visibility_turns,int encoding_empire) const3032 void Universe::GetEmpireObjectVisibilityTurnMap(EmpireObjectVisibilityTurnMap& empire_object_visibility_turns,
3033 int encoding_empire) const
3034 {
3035 if (encoding_empire == ALL_EMPIRES) {
3036 empire_object_visibility_turns = m_empire_object_visibility_turns;
3037 return;
3038 }
3039
3040 // include just requested empire's visibility turn information
3041 empire_object_visibility_turns.clear();
3042 auto it = m_empire_object_visibility_turns.find(encoding_empire);
3043 if (it != m_empire_object_visibility_turns.end())
3044 empire_object_visibility_turns[encoding_empire] = it->second;
3045 }
3046
GetEmpireKnownDestroyedObjects(ObjectKnowledgeMap & empire_known_destroyed_object_ids,int encoding_empire) const3047 void Universe::GetEmpireKnownDestroyedObjects(ObjectKnowledgeMap& empire_known_destroyed_object_ids,
3048 int encoding_empire) const
3049 {
3050 if (&empire_known_destroyed_object_ids == &m_empire_known_destroyed_object_ids)
3051 return;
3052
3053 if (encoding_empire == ALL_EMPIRES) {
3054 empire_known_destroyed_object_ids = m_empire_known_destroyed_object_ids;
3055 return;
3056 }
3057
3058 empire_known_destroyed_object_ids.clear();
3059
3060 // copy info about what encoding empire knows
3061 auto it = m_empire_known_destroyed_object_ids.find(encoding_empire);
3062 if (it != m_empire_known_destroyed_object_ids.end())
3063 empire_known_destroyed_object_ids[encoding_empire] = it->second;
3064 }
3065
GetEmpireStaleKnowledgeObjects(ObjectKnowledgeMap & empire_stale_knowledge_object_ids,int encoding_empire) const3066 void Universe::GetEmpireStaleKnowledgeObjects(ObjectKnowledgeMap& empire_stale_knowledge_object_ids,
3067 int encoding_empire) const
3068 {
3069 if (&empire_stale_knowledge_object_ids == &m_empire_stale_knowledge_object_ids)
3070 return;
3071
3072 if (encoding_empire == ALL_EMPIRES) {
3073 empire_stale_knowledge_object_ids = m_empire_stale_knowledge_object_ids;
3074 return;
3075 }
3076
3077 empire_stale_knowledge_object_ids.clear();
3078
3079 // copy stale data for this empire
3080 auto it = m_empire_stale_knowledge_object_ids.find(encoding_empire);
3081 if (it != m_empire_stale_knowledge_object_ids.end())
3082 empire_stale_knowledge_object_ids[encoding_empire] = it->second;
3083 }
3084
CheckSumContent()3085 std::map<std::string, unsigned int> CheckSumContent() {
3086 std::map<std::string, unsigned int> checksums;
3087
3088 // add entries for various content managers...
3089 checksums["BuildingTypeManager"] = GetBuildingTypeManager().GetCheckSum();
3090 checksums["Encyclopedia"] = GetEncyclopedia().GetCheckSum();
3091 checksums["FieldTypeManager"] = GetFieldTypeManager().GetCheckSum();
3092 checksums["ShipHullManager"] = GetShipHullManager().GetCheckSum();
3093 checksums["ShipPartManager"] = GetShipPartManager().GetCheckSum();
3094 checksums["PredefinedShipDesignManager"] = GetPredefinedShipDesignManager().GetCheckSum();
3095 checksums["SpeciesManager"] = GetSpeciesManager().GetCheckSum();
3096 checksums["TechManager"] = GetTechManager().GetCheckSum();
3097
3098 return checksums;
3099 }
3100