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