1 #include "Conditions.h"
2 
3 #include "../util/Logger.h"
4 #include "../util/Random.h"
5 #include "../util/i18n.h"
6 #include "../util/ScopedTimer.h"
7 #include "UniverseObject.h"
8 #include "Pathfinder.h"
9 #include "Universe.h"
10 #include "Building.h"
11 #include "BuildingType.h"
12 #include "Fighter.h"
13 #include "Fleet.h"
14 #include "Ship.h"
15 #include "ShipDesign.h"
16 #include "ShipPart.h"
17 #include "ShipHull.h"
18 #include "ObjectMap.h"
19 #include "Planet.h"
20 #include "System.h"
21 #include "Species.h"
22 #include "Special.h"
23 #include "Meter.h"
24 #include "ValueRefs.h"
25 #include "Enums.h"
26 #include "../Empire/Empire.h"
27 #include "../Empire/EmpireManager.h"
28 #include "../Empire/Supply.h"
29 
30 #include <boost/algorithm/cxx11/all_of.hpp>
31 #include <boost/algorithm/string/case_conv.hpp>
32 #include <boost/graph/adjacency_list.hpp>
33 #include <boost/graph/st_connected.hpp>
34 
35 #include <cfloat>
36 
37 using boost::io::str;
38 
39 FO_COMMON_API extern const int INVALID_DESIGN_ID;
40 
41 bool UserStringExists(const std::string& str);
42 
43 namespace {
44     const std::string EMPTY_STRING;
45 
46     DeclareThreadSafeLogger(conditions);
47 
48 #if BOOST_VERSION >= 106000
49     using boost::placeholders::_1;
50 #endif
51 
AddAllObjectsSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)52     void AddAllObjectsSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
53         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingObjects().size());
54         std::transform(objects.ExistingObjects().begin(), objects.ExistingObjects().end(),
55                        std::back_inserter(condition_non_targets),
56                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
57     }
58 
AddBuildingSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)59     void AddBuildingSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
60         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingBuildings().size());
61         std::transform(objects.ExistingBuildings().begin(), objects.ExistingBuildings().end(),
62                        std::back_inserter(condition_non_targets),
63                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
64     }
65 
AddFieldSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)66     void AddFieldSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
67         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingFields().size());
68         std::transform(objects.ExistingFields().begin(), objects.ExistingFields().end(),
69                        std::back_inserter(condition_non_targets),
70                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
71     }
72 
AddFleetSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)73     void AddFleetSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
74         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingFleets().size());
75         std::transform(objects.ExistingFleets().begin(), objects.ExistingFleets().end(),
76                        std::back_inserter(condition_non_targets),
77                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
78     }
79 
AddPlanetSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)80     void AddPlanetSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
81         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingPlanets().size());
82         std::transform(objects.ExistingPlanets().begin(), objects.ExistingPlanets().end(),
83                        std::back_inserter(condition_non_targets),
84                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
85     }
86 
AddPopCenterSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)87     void AddPopCenterSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
88         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingPopCenters().size());
89         std::transform(objects.ExistingPopCenters().begin(), objects.ExistingPopCenters().end(),
90                        std::back_inserter(condition_non_targets),
91                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
92     }
93 
AddResCenterSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)94     void AddResCenterSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
95         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingResourceCenters().size());
96         std::transform(objects.ExistingResourceCenters().begin(), objects.ExistingResourceCenters().end(),
97                        std::back_inserter(condition_non_targets),
98                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
99     }
100 
AddShipSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)101     void AddShipSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
102         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingShips().size());
103         std::transform(objects.ExistingShips().begin(), objects.ExistingShips().end(),
104                        std::back_inserter(condition_non_targets),
105                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
106     }
107 
AddSystemSet(const ObjectMap & objects,Condition::ObjectSet & condition_non_targets)108     void AddSystemSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) {
109         condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingSystems().size());
110         std::transform(objects.ExistingSystems().begin(), objects.ExistingSystems().end(),
111                        std::back_inserter(condition_non_targets),
112                        boost::bind(&std::map<int, std::shared_ptr<const UniverseObject>>::value_type::second, _1));
113     }
114 
115     /** Used by 4-parameter Condition::Eval function, and some of its
116       * overrides, to scan through \a matches or \a non_matches set and apply
117       * \a pred to each object, to test if it should remain in its current set
118       * or be transferred from the \a search_domain specified set into the
119       * other. */
120     template <typename Pred>
EvalImpl(Condition::ObjectSet & matches,Condition::ObjectSet & non_matches,Condition::SearchDomain search_domain,const Pred & pred)121     void EvalImpl(Condition::ObjectSet& matches, Condition::ObjectSet& non_matches,
122                   Condition::SearchDomain search_domain, const Pred& pred)
123     {
124         auto& from_set = search_domain == Condition::MATCHES ? matches : non_matches;
125         auto& to_set = search_domain == Condition::MATCHES ? non_matches : matches;
126         for (auto it = from_set.begin(); it != from_set.end(); ) {
127             bool match = pred(*it);
128             if ((search_domain == Condition::MATCHES && !match) ||
129                 (search_domain == Condition::NON_MATCHES && match))
130             {
131                 to_set.push_back(*it);
132                 *it = from_set.back();
133                 from_set.pop_back();
134             } else {
135                 ++it;
136             }
137         }
138     }
139 
FlattenAndNestedConditions(const std::vector<Condition::Condition * > & input_conditions)140     std::vector<Condition::Condition*> FlattenAndNestedConditions(
141         const std::vector<Condition::Condition*>& input_conditions)
142     {
143         std::vector<Condition::Condition*> retval;
144         for (Condition::Condition* condition : input_conditions) {
145             if (Condition::And* and_condition = dynamic_cast<Condition::And*>(condition)) {
146                 std::vector<Condition::Condition*> flattened_operands =
147                     FlattenAndNestedConditions(and_condition->Operands());
148                 std::copy(flattened_operands.begin(), flattened_operands.end(), std::back_inserter(retval));
149             } else {
150                 if (condition)
151                     retval.push_back(condition);
152             }
153         }
154         return retval;
155     }
156 
ConditionDescriptionAndTest(const std::vector<Condition::Condition * > & conditions,const ScriptingContext & parent_context,std::shared_ptr<const UniverseObject> candidate_object)157     std::map<std::string, bool> ConditionDescriptionAndTest(
158         const std::vector<Condition::Condition*>& conditions,
159         const ScriptingContext& parent_context,
160         std::shared_ptr<const UniverseObject> candidate_object/* = nullptr*/)
161     {
162         std::map<std::string, bool> retval;
163 
164         std::vector<Condition::Condition*> flattened_conditions;
165         if (conditions.empty())
166             return retval;
167         else if (conditions.size() > 1 || dynamic_cast<Condition::And*>(*conditions.begin()))
168             flattened_conditions = FlattenAndNestedConditions(conditions);
169         //else if (dynamic_cast<const Condition::Or*>(*conditions.begin()))
170         //    flattened_conditions = FlattenOrNestedConditions(conditions);
171         else
172             flattened_conditions = conditions;
173 
174         for (Condition::Condition* condition : flattened_conditions) {
175             retval[condition->Description()] = condition->Eval(parent_context, candidate_object);
176         }
177         return retval;
178     }
179 }
180 
181 namespace Condition {
ConditionFailedDescription(const std::vector<Condition * > & conditions,std::shared_ptr<const UniverseObject> candidate_object,std::shared_ptr<const UniverseObject> source_object)182 std::string ConditionFailedDescription(const std::vector<Condition*>& conditions,
183                                        std::shared_ptr<const UniverseObject> candidate_object/* = nullptr*/,
184                                        std::shared_ptr<const UniverseObject> source_object/* = nullptr*/)
185 {
186     if (conditions.empty())
187         return UserString("NONE");
188 
189     std::string retval;
190 
191     // test candidate against all input conditions, and store descriptions of each
192     for (const auto& result : ConditionDescriptionAndTest(conditions, ScriptingContext(source_object), candidate_object)) {
193             if (!result.second)
194                  retval += UserString("FAILED") + " <rgba 255 0 0 255>" + result.first +"</rgba>\n";
195     }
196 
197     // remove empty line from the end of the string
198     retval = retval.substr(0, retval.length() - 1);
199 
200     return retval;
201 }
202 
ConditionDescription(const std::vector<Condition * > & conditions,std::shared_ptr<const UniverseObject> candidate_object,std::shared_ptr<const UniverseObject> source_object)203 std::string ConditionDescription(const std::vector<Condition*>& conditions,
204                                  std::shared_ptr<const UniverseObject> candidate_object/* = nullptr*/,
205                                  std::shared_ptr<const UniverseObject> source_object/* = nullptr*/)
206 {
207     if (conditions.empty())
208         return UserString("NONE");
209 
210     // test candidate against all input conditions, and store descriptions of each
211     auto condition_description_and_test_results =
212         ConditionDescriptionAndTest(conditions, ScriptingContext(source_object), candidate_object);
213     bool all_conditions_match_candidate = true, at_least_one_condition_matches_candidate = false;
214     for (const auto& result : condition_description_and_test_results) {
215         all_conditions_match_candidate = all_conditions_match_candidate && result.second;
216         at_least_one_condition_matches_candidate = at_least_one_condition_matches_candidate || result.second;
217     }
218 
219     // concatenate (non-duplicated) single-description results
220     std::string retval;
221     if (conditions.size() > 1 || dynamic_cast<And*>(*conditions.begin())) {
222         retval += UserString("ALL_OF") + " ";
223         retval += (all_conditions_match_candidate ? UserString("PASSED") : UserString("FAILED")) + "\n";
224     } else if (dynamic_cast<Or*>(*conditions.begin())) {
225         retval += UserString("ANY_OF") + " ";
226         retval += (at_least_one_condition_matches_candidate ? UserString("PASSED") : UserString("FAILED")) + "\n";
227     }
228     // else just output single condition description and PASS/FAIL text
229 
230     for (const auto& result : condition_description_and_test_results) {
231         retval += (result.second ? UserString("PASSED") : UserString("FAILED"));
232         retval += " " + result.first + "\n";
233     }
234     return retval;
235 }
236 
237 #define CHECK_COND_VREF_MEMBER(m_ptr) { if (m_ptr == rhs_.m_ptr) {              \
238                                             /* check next member */             \
239                                         } else if (!m_ptr || !rhs_.m_ptr) {     \
240                                             return false;                       \
241                                         } else {                                \
242                                             if (*m_ptr != *(rhs_.m_ptr))        \
243                                                 return false;                   \
244                                         }   }
245 
246 ///////////////////////////////////////////////////////////
247 // Condition                                         //
248 ///////////////////////////////////////////////////////////
249 struct Condition::MatchHelper {
MatchHelperCondition::Condition::MatchHelper250     MatchHelper(const Condition* this_, const ScriptingContext& parent_context) :
251         m_this(this_),
252         m_parent_context(parent_context)
253     {}
254 
operator ()Condition::Condition::MatchHelper255     bool operator()(std::shared_ptr<const UniverseObject> candidate) const
256     { return m_this->Match(ScriptingContext(m_parent_context, candidate)); }
257 
258     const Condition* m_this;
259     const ScriptingContext& m_parent_context;
260 };
261 
262 Condition::~Condition() = default;
263 
operator ==(const Condition & rhs) const264 bool Condition::operator==(const Condition& rhs) const {
265     if (this == &rhs)
266         return true;
267 
268     if (typeid(*this) != typeid(rhs))
269         return false;
270 
271     return true;
272 }
273 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const274 void Condition::Eval(const ScriptingContext& parent_context,
275                      ObjectSet& matches, ObjectSet& non_matches,
276                      SearchDomain search_domain/* = NON_MATCHES*/) const
277 { EvalImpl(matches, non_matches, search_domain, MatchHelper(this, parent_context)); }
278 
Eval(const ScriptingContext & parent_context,Effect::TargetSet & matches,Effect::TargetSet & non_matches,SearchDomain search_domain) const279 void Condition::Eval(const ScriptingContext& parent_context,
280                      Effect::TargetSet& matches, Effect::TargetSet& non_matches,
281                      SearchDomain search_domain/* = NON_MATCHES*/) const
282 {
283     // reinterpret sets of mutable objects as sets of non-mutable objects.
284     auto& matches_as_objectset = reinterpret_cast<ObjectSet&>(matches);
285     auto& non_matches_as_objectset = reinterpret_cast<ObjectSet&>(non_matches);
286     this->Eval(parent_context, matches_as_objectset, non_matches_as_objectset, search_domain);
287 }
288 
Eval(const ScriptingContext & parent_context,ObjectSet & matches) const289 void Condition::Eval(const ScriptingContext& parent_context,
290                      ObjectSet& matches) const
291 {
292     matches.clear();
293     ObjectSet condition_initial_candidates;
294 
295     // evaluate condition only on objects that could potentially be matched by the condition
296     GetDefaultInitialCandidateObjects(parent_context, condition_initial_candidates);
297 
298     matches.reserve(condition_initial_candidates.size());
299     Eval(parent_context, matches, condition_initial_candidates);
300 }
301 
Eval(const ScriptingContext & parent_context,Effect::TargetSet & matches) const302 void Condition::Eval(const ScriptingContext& parent_context,
303                      Effect::TargetSet& matches) const
304 {
305     // reinterpret sets of mutable objects as sets of non-mutable objects.
306     auto& matches_as_objectset = reinterpret_cast<ObjectSet&>(matches);
307     this->Eval(parent_context, matches_as_objectset);
308 }
309 
Eval(const ScriptingContext & parent_context,std::shared_ptr<const UniverseObject> candidate) const310 bool Condition::Eval(const ScriptingContext& parent_context,
311                      std::shared_ptr<const UniverseObject> candidate) const
312 {
313     if (!candidate)
314         return false;
315     ObjectSet non_matches, matches;
316     non_matches.push_back(candidate);
317     Eval(parent_context, matches, non_matches);
318     return non_matches.empty(); // if candidate has been matched, non_matches will now be empty
319 }
320 
Eval(std::shared_ptr<const UniverseObject> candidate) const321 bool Condition::Eval(std::shared_ptr<const UniverseObject> candidate) const {
322     if (!candidate)
323         return false;
324     ObjectSet non_matches, matches;
325     non_matches.push_back(candidate);
326     Eval(ScriptingContext(), matches, non_matches);
327     return non_matches.empty(); // if candidate has been matched, non_matches will now be empty
328 }
329 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const330 void Condition::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
331                                                   ObjectSet& condition_non_targets) const
332 { AddAllObjectsSet(parent_context.ContextObjects(), condition_non_targets); }
333 
Description(bool negated) const334 std::string Condition::Description(bool negated/* = false*/) const
335 { return ""; }
336 
Dump(unsigned short ntabs) const337 std::string Condition::Dump(unsigned short ntabs) const
338 { return ""; }
339 
Match(const ScriptingContext & local_context) const340 bool Condition::Match(const ScriptingContext& local_context) const
341 { return false; }
342 
343 ///////////////////////////////////////////////////////////
344 // Number                                                //
345 ///////////////////////////////////////////////////////////
Number(std::unique_ptr<ValueRef::ValueRef<int>> && low,std::unique_ptr<ValueRef::ValueRef<int>> && high,std::unique_ptr<Condition> && condition)346 Number::Number(std::unique_ptr<ValueRef::ValueRef<int>>&& low,
347                std::unique_ptr<ValueRef::ValueRef<int>>&& high,
348                std::unique_ptr<Condition>&& condition) :
349     m_low(std::move(low)),
350     m_high(std::move(high)),
351     m_condition(std::move(condition))
352 {
353     auto operands = {m_low.get(), m_high.get()};
354     m_root_candidate_invariant =
355         m_condition->RootCandidateInvariant() &&
356         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
357     m_target_invariant =
358         m_condition->TargetInvariant() &&
359         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
360     m_source_invariant =
361         m_condition->SourceInvariant() &&
362         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
363 }
364 
operator ==(const Condition & rhs) const365 bool Number::operator==(const Condition& rhs) const {
366     if (this == &rhs)
367         return true;
368     if (typeid(*this) != typeid(rhs))
369         return false;
370 
371     const Number& rhs_ = static_cast<const Number&>(rhs);
372 
373     CHECK_COND_VREF_MEMBER(m_low)
374     CHECK_COND_VREF_MEMBER(m_high)
375     CHECK_COND_VREF_MEMBER(m_condition)
376 
377     return true;
378 }
379 
Description(bool negated) const380 std::string Number::Description(bool negated/* = false*/) const {
381     std::string low_str = (m_low ? (m_low->ConstantExpr() ?
382                                     std::to_string(m_low->Eval()) :
383                                     m_low->Description())
384                                  : "0");
385     std::string high_str = (m_high ? (m_high->ConstantExpr() ?
386                                       std::to_string(m_high->Eval()) :
387                                       m_high->Description())
388                                    : std::to_string(INT_MAX));
389 
390     const std::string& description_str = (!negated)
391         ? UserString("DESC_NUMBER")
392         : UserString("DESC_NUMBER_NOT");
393     return str(FlexibleFormat(description_str)
394                % low_str
395                % high_str
396                % m_condition->Description());
397 }
398 
Dump(unsigned short ntabs) const399 std::string Number::Dump(unsigned short ntabs) const {
400     std::string retval = DumpIndent(ntabs) + "Number";
401     if (m_low)
402         retval += " low = " + m_low->Dump(ntabs);
403     if (m_high)
404         retval += " high = " + m_high->Dump(ntabs);
405     retval += " condition =\n";
406     retval += m_condition->Dump(ntabs+1);
407     return retval;
408 }
409 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const410 void Number::Eval(const ScriptingContext& parent_context,
411                   ObjectSet& matches, ObjectSet& non_matches,
412                   SearchDomain search_domain/* = NON_MATCHES*/) const
413 {
414     // Number does not have a single valid local candidate to be matched, as it
415     // will match anything if the proper number of objects match the subcondition.
416 
417     if (!(
418                 (!m_low  || m_low->LocalCandidateInvariant())
419              && (!m_high || m_high->LocalCandidateInvariant())
420          )
421        )
422     {
423         ErrorLogger() << "Condition::Number::Eval has local candidate-dependent ValueRefs, but no valid local candidate!";
424     } else if (
425                 !parent_context.condition_root_candidate
426                 && !(
427                         (!m_low  || m_low->RootCandidateInvariant())
428                      && (!m_high || m_high->RootCandidateInvariant())
429                     )
430               )
431     {
432         ErrorLogger() << "Condition::Number::Eval has root candidate-dependent ValueRefs, but expects local candidate to be the root candidate, and has no valid local candidate!";
433     }
434 
435     if (!parent_context.condition_root_candidate && !this->RootCandidateInvariant()) {
436         // no externally-defined root candidate, so each object matched must
437         // separately act as a root candidate, and sub-condition must be re-
438         // evaluated for each tested object and the number of objects matched
439         // checked for each object being tested
440         Condition::Eval(parent_context, matches, non_matches, search_domain);
441 
442     } else {
443         // Matching for this condition doesn't need to check each candidate object against
444         // the number of subcondition matches, so don't need to use EvalImpl
445         bool in_range = Match(parent_context);
446 
447         // transfer objects to or from candidate set, according to whether
448         // number of matches was within the requested range.
449         if (search_domain == MATCHES && !in_range) {
450             // move all objects from matches to non_matches
451             non_matches.insert(non_matches.end(), matches.begin(), matches.end());
452             matches.clear();
453         } else if (search_domain == NON_MATCHES && in_range) {
454             // move all objects from non_matches to matches
455             matches.insert(matches.end(), non_matches.begin(), non_matches.end());
456             non_matches.clear();
457         }
458     }
459 }
460 
Match(const ScriptingContext & local_context) const461 bool Number::Match(const ScriptingContext& local_context) const {
462     // get acceptable range of subcondition matches for candidate
463     int low = (m_low ? std::max(0, m_low->Eval(local_context)) : 0);
464     int high = (m_high ? std::min(m_high->Eval(local_context), INT_MAX) : INT_MAX);
465 
466     // get set of all UniverseObjects that satisfy m_condition
467     ObjectSet condition_matches;
468     m_condition->Eval(local_context, condition_matches);
469 
470     // compare number of objects that satisfy m_condition to the acceptable range of such objects
471     int matched = condition_matches.size();
472     bool in_range = (low <= matched && matched <= high);
473     return in_range;
474 }
475 
SetTopLevelContent(const std::string & content_name)476 void Number::SetTopLevelContent(const std::string& content_name) {
477     if (m_low)
478         m_low->SetTopLevelContent(content_name);
479     if (m_high)
480         m_high->SetTopLevelContent(content_name);
481     if (m_condition)
482         m_condition->SetTopLevelContent(content_name);
483 }
484 
GetCheckSum() const485 unsigned int Number::GetCheckSum() const {
486     unsigned int retval{0};
487 
488     CheckSums::CheckSumCombine(retval, "Condition::Number");
489     CheckSums::CheckSumCombine(retval, m_low);
490     CheckSums::CheckSumCombine(retval, m_high);
491     CheckSums::CheckSumCombine(retval, m_condition);
492 
493     TraceLogger() << "GetCheckSum(Number): retval: " << retval;
494     return retval;
495 }
496 
497 ///////////////////////////////////////////////////////////
498 // Turn                                                  //
499 ///////////////////////////////////////////////////////////
Turn(std::unique_ptr<ValueRef::ValueRef<int>> && low,std::unique_ptr<ValueRef::ValueRef<int>> && high)500 Turn::Turn(std::unique_ptr<ValueRef::ValueRef<int>>&& low,
501            std::unique_ptr<ValueRef::ValueRef<int>>&& high) :
502     m_low(std::move(low)),
503     m_high(std::move(high))
504 {
505     auto operands = {m_low.get(), m_high.get()};
506     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
507     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
508     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
509 }
510 
operator ==(const Condition & rhs) const511 bool Turn::operator==(const Condition& rhs) const {
512     if (this == &rhs)
513         return true;
514     if (typeid(*this) != typeid(rhs))
515         return false;
516 
517     const Turn& rhs_ = static_cast<const Turn&>(rhs);
518 
519     CHECK_COND_VREF_MEMBER(m_low)
520     CHECK_COND_VREF_MEMBER(m_high)
521 
522     return true;
523 }
524 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const525 void Turn::Eval(const ScriptingContext& parent_context,
526                 ObjectSet& matches, ObjectSet& non_matches,
527                 SearchDomain search_domain/* = NON_MATCHES*/) const
528 {
529     // if ValueRef for low or high range limits depend on local candidate, then
530     // they must be evaluated per-candidate.
531     // if there already is a root candidate, then this condition's parameters
532     // can be evaluated assuming it will not change.
533     // if there is no root candidate in the parent context, then this
534     // condition's candidates will be the root candidates, and this condition's
535     // parameters must be root candidate invariant or else must be evaluated
536     // per-candidate
537     bool simple_eval_safe = ((!m_low || m_low->LocalCandidateInvariant()) &&
538                              (!m_high || m_high->LocalCandidateInvariant()) &&
539                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
540     if (simple_eval_safe) {
541         // Matching for this condition doesn't need to check each candidate object against
542         // the turn number separately, so don't need to use EvalImpl
543         bool match = Match(parent_context);
544 
545         // transfer objects to or from candidate set, according to whether the
546         // current turn was within the requested range.
547         if (match && search_domain == NON_MATCHES) {
548             // move all objects from non_matches to matches
549             matches.insert(matches.end(), non_matches.begin(), non_matches.end());
550             non_matches.clear();
551         } else if (!match && search_domain == MATCHES) {
552             // move all objects from matches to non_matches
553             non_matches.insert(non_matches.end(), matches.begin(), matches.end());
554             matches.clear();
555         }
556     } else {
557         // re-evaluate allowed turn range for each candidate object
558         Condition::Eval(parent_context, matches, non_matches, search_domain);
559     }
560 }
561 
Description(bool negated) const562 std::string Turn::Description(bool negated/* = false*/) const {
563     std::string low_str;
564     if (m_low)
565         low_str = (m_low->ConstantExpr() ?
566                    std::to_string(m_low->Eval()) :
567                    m_low->Description());
568     std::string high_str;
569     if (m_high)
570         high_str = (m_high->ConstantExpr() ?
571                     std::to_string(m_high->Eval()) :
572                     m_high->Description());
573     std::string description_str;
574 
575     if (m_low && m_high) {
576         description_str = (!negated)
577             ? UserString("DESC_TURN")
578             : UserString("DESC_TURN_NOT");
579         return str(FlexibleFormat(description_str)
580                    % low_str
581                    % high_str);
582 
583     } else if (m_low) {
584         description_str = (!negated)
585             ? UserString("DESC_TURN_MIN_ONLY")
586             : UserString("DESC_TURN_MIN_ONLY_NOT");
587         return str(FlexibleFormat(description_str)
588                    % low_str);
589 
590     } else if (m_high) {
591         description_str = (!negated)
592             ? UserString("DESC_TURN_MAX_ONLY")
593             : UserString("DESC_TURN_MAX_ONLY_NOT");
594         return str(FlexibleFormat(description_str)
595                    % high_str);
596 
597     } else {
598         return (!negated)
599             ? UserString("DESC_TURN_ANY")
600             : UserString("DESC_TURN_ANY_NOT");
601     }
602 }
603 
Dump(unsigned short ntabs) const604 std::string Turn::Dump(unsigned short ntabs) const {
605     std::string retval = DumpIndent(ntabs) + "Turn";
606     if (m_low)
607         retval += " low = " + m_low->Dump(ntabs);
608     if (m_high)
609         retval += " high = " + m_high->Dump(ntabs);
610     retval += "\n";
611     return retval;
612 }
613 
Match(const ScriptingContext & local_context) const614 bool Turn::Match(const ScriptingContext& local_context) const {
615     int low =  (m_low ?  std::max(BEFORE_FIRST_TURN,           m_low->Eval(local_context)) : BEFORE_FIRST_TURN);
616     int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) :      IMPOSSIBLY_LARGE_TURN);
617     int turn = CurrentTurn();
618     return (low <= turn && turn <= high);
619 }
620 
SetTopLevelContent(const std::string & content_name)621 void Turn::SetTopLevelContent(const std::string& content_name) {
622     if (m_low)
623         m_low->SetTopLevelContent(content_name);
624     if (m_high)
625         m_high->SetTopLevelContent(content_name);
626 }
627 
GetCheckSum() const628 unsigned int Turn::GetCheckSum() const {
629     unsigned int retval{0};
630 
631     CheckSums::CheckSumCombine(retval, "Condition::Turn");
632     CheckSums::CheckSumCombine(retval, m_low);
633     CheckSums::CheckSumCombine(retval, m_high);
634 
635     TraceLogger() << "GetCheckSum(Turn): retval: " << retval;
636     return retval;
637 }
638 
639 ///////////////////////////////////////////////////////////
640 // SortedNumberOf                                        //
641 ///////////////////////////////////////////////////////////
SortedNumberOf(std::unique_ptr<ValueRef::ValueRef<int>> && number,std::unique_ptr<Condition> && condition)642 SortedNumberOf::SortedNumberOf(std::unique_ptr<ValueRef::ValueRef<int>>&& number,
643                                std::unique_ptr<Condition>&& condition) :
644     SortedNumberOf(std::move(number), nullptr, SORT_RANDOM, std::move(condition))
645 {}
646 
SortedNumberOf(std::unique_ptr<ValueRef::ValueRef<int>> && number,std::unique_ptr<ValueRef::ValueRef<double>> && sort_key_ref,SortingMethod sorting_method,std::unique_ptr<Condition> && condition)647 SortedNumberOf::SortedNumberOf(std::unique_ptr<ValueRef::ValueRef<int>>&& number,
648                                std::unique_ptr<ValueRef::ValueRef<double>>&& sort_key_ref,
649                                SortingMethod sorting_method,
650                                std::unique_ptr<Condition>&& condition) :
651     m_number(std::move(number)),
652     m_sort_key(std::move(sort_key_ref)),
653     m_sorting_method(sorting_method),
654     m_condition(std::move(condition))
655 {
656     m_root_candidate_invariant =
657         (!m_number || m_number->RootCandidateInvariant()) &&
658         (!m_sort_key || m_sort_key->RootCandidateInvariant()) &&
659         (!m_condition || m_condition->RootCandidateInvariant());
660     m_target_invariant =
661         (!m_number || m_number->TargetInvariant()) &&
662         (!m_sort_key || m_sort_key->TargetInvariant()) &&
663         (!m_condition || m_condition->TargetInvariant());
664     m_source_invariant =
665         (!m_number || m_number->SourceInvariant()) &&
666         (!m_sort_key || m_sort_key->SourceInvariant()) &&
667         (!m_condition || m_condition->SourceInvariant());
668 
669 }
670 
operator ==(const Condition & rhs) const671 bool SortedNumberOf::operator==(const Condition& rhs) const {
672     if (this == &rhs)
673         return true;
674     if (typeid(*this) != typeid(rhs))
675         return false;
676 
677     const SortedNumberOf& rhs_ = static_cast<const SortedNumberOf&>(rhs);
678 
679     if (m_sorting_method != rhs_.m_sorting_method)
680         return false;
681 
682     CHECK_COND_VREF_MEMBER(m_number)
683     CHECK_COND_VREF_MEMBER(m_sort_key)
684     CHECK_COND_VREF_MEMBER(m_condition)
685 
686     return true;
687 }
688 
689 namespace {
690     /** Random number genrator function to use with random_shuffle */
CustomRandInt(int max_plus_one)691     int CustomRandInt(int max_plus_one)
692     { return RandSmallInt(0, max_plus_one - 1); }
693     int (*CRI)(int) = CustomRandInt;
694 
695     /** Transfers the indicated \a number of objects, randomly selected from from_set to to_set */
TransferRandomObjects(unsigned int number,ObjectSet & from_set,ObjectSet & to_set)696     void TransferRandomObjects(unsigned int number, ObjectSet& from_set, ObjectSet& to_set) {
697         // ensure number of objects to be moved is within reasonable range
698         number = std::min<unsigned int>(number, from_set.size());
699         if (number == 0)
700             return;
701 
702         // create list of bool flags to indicate whether each item in from_set
703         // with corresponding place in iteration order should be transfered
704         std::vector<bool> transfer_flags(from_set.size(), false);   // initialized to all false
705 
706         // set first  number  flags to true
707         std::fill_n(transfer_flags.begin(), number, true);
708 
709         // shuffle flags to randomize which flags are set
710         std::random_shuffle(transfer_flags.begin(), transfer_flags.end(), CRI);
711 
712         // transfer objects that have been flagged
713         int i = 0;
714         for (auto it = from_set.begin(); it != from_set.end(); ++i) {
715             if (transfer_flags[i]) {
716                 to_set.push_back(*it);
717                 *it = from_set.back();
718                 from_set.pop_back();
719             } else {
720                 ++it;
721             }
722         }
723     }
724 
725     /** Transfers the indicated \a number of objects, selected from \a from_set
726       * into \a to_set.  The objects transferred are selected based on the value
727       * of \a sort_key evaluated on them, with the largest / smallest / most
728       * common sort keys chosen, or a random selection chosen, depending on the
729       * specified \a sorting_method */
TransferSortedObjects(unsigned int number,ValueRef::ValueRef<double> * sort_key,const ScriptingContext & context,SortingMethod sorting_method,ObjectSet & from_set,ObjectSet & to_set)730     void TransferSortedObjects(unsigned int number, ValueRef::ValueRef<double>* sort_key,
731                                const ScriptingContext& context, SortingMethod sorting_method,
732                                ObjectSet& from_set, ObjectSet& to_set)
733     {
734         // handle random case, which doesn't need sorting key
735         if (sorting_method == SORT_RANDOM) {
736             TransferRandomObjects(number, from_set, to_set);
737             return;
738         }
739 
740         // for other SoringMethods, need sort key values
741         if (!sort_key) {
742             ErrorLogger() << "TransferSortedObjects given null sort_key";
743             return;
744         }
745 
746         // get sort key values for all objects in from_set, and sort by inserting into map
747         std::multimap<float, std::shared_ptr<const UniverseObject>> sort_key_objects;
748         for (auto& from : from_set) {
749             float sort_value = sort_key->Eval(ScriptingContext(context, from));
750             sort_key_objects.insert({sort_value, from});
751         }
752 
753         // how many objects to select?
754         number = std::min<unsigned int>(number, sort_key_objects.size());
755         if (number == 0)
756             return;
757         unsigned int number_transferred(0);
758 
759         // pick max / min / most common values
760         if (sorting_method == SORT_MIN) {
761             // move (number) objects with smallest sort key (at start of map)
762             // from the from_set into the to_set.
763             for (const auto& entry : sort_key_objects) {
764                 auto object_to_transfer = entry.second;
765                 auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer);
766                 if (from_it != from_set.end()) {
767                     *from_it = from_set.back();
768                     from_set.pop_back();
769                     to_set.push_back(object_to_transfer);
770                     number_transferred++;
771                     if (number_transferred >= number)
772                         return;
773                 }
774             }
775 
776         } else if (sorting_method == SORT_MAX) {
777             // move (number) objects with largest sort key (at end of map)
778             // from the from_set into the to_set.
779             for (auto sorted_it = sort_key_objects.rbegin();  // would use const_reverse_iterator but this causes a compile error in some compilers
780                  sorted_it != sort_key_objects.rend(); ++sorted_it)
781             {
782                 auto object_to_transfer = sorted_it->second;
783                 auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer);
784                 if (from_it != from_set.end()) {
785                     *from_it = from_set.back();
786                     from_set.pop_back();
787                     to_set.push_back(object_to_transfer);
788                     number_transferred++;
789                     if (number_transferred >= number)
790                         return;
791                 }
792             }
793 
794         } else if (sorting_method == SORT_MODE) {
795             // compile histogram of of number of times each sort key occurs
796             std::map<float, unsigned int> histogram;
797             for (const auto& entry : sort_key_objects) {
798                 histogram[entry.first]++;
799             }
800 
801             // invert histogram to index by number of occurances
802             std::multimap<unsigned int, float> inv_histogram;
803             for (const auto& entry : histogram)
804                 inv_histogram.insert({entry.second, entry.first});
805 
806             // reverse-loop through inverted histogram to find which sort keys
807             // occurred most frequently, and transfer objects with those sort
808             // keys from from_set to to_set.
809             for (auto inv_hist_it = inv_histogram.rbegin();  // would use const_reverse_iterator but this causes a compile error in some compilers
810                  inv_hist_it != inv_histogram.rend(); ++inv_hist_it)
811             {
812                 float cur_sort_key = inv_hist_it->second;
813 
814                 // get range of objects with the current sort key
815                 auto key_range = sort_key_objects.equal_range(cur_sort_key);
816 
817                 // loop over range, selecting objects to transfer from from_set to to_set
818                 for (auto sorted_it = key_range.first;
819                      sorted_it != key_range.second; ++sorted_it)
820                 {
821                     auto object_to_transfer = sorted_it->second;
822                     auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer);
823                     if (from_it != from_set.end()) {
824                         *from_it = from_set.back();
825                         from_set.pop_back();
826                         to_set.push_back(object_to_transfer);
827                         number_transferred++;
828                         if (number_transferred >= number)
829                             return;
830                     }
831                 }
832             }
833 
834         } else {
835              ErrorLogger() << "TransferSortedObjects given unknown sort method";
836         }
837     }
838 }
839 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const840 void SortedNumberOf::Eval(const ScriptingContext& parent_context,
841                           ObjectSet& matches, ObjectSet& non_matches,
842                           SearchDomain search_domain/* = NON_MATCHES*/) const
843 {
844     // Most conditions match objects independently of the other objects being
845     // tested, but the number parameter for NumberOf conditions makes things
846     // more complicated.  In order to match some number of the potential
847     // matches property, both the matches and non_matches need to be checked
848     // against the subcondition, and the total number of subcondition matches
849     // counted.
850     // Then, when searching NON_MATCHES, non_matches may be moved into matches
851     // so that the number of subcondition matches in matches is equal to the
852     // requested number.  There may also be subcondition non-matches in
853     // matches, but these are not counted or affected by this condition.
854     // Or, when searching MATCHES, matches may be moved into non_matches so
855     // that the number of subcondition matches in matches is equal to the
856     // requested number.  There again may be subcondition non-matches in
857     // matches, but these are also not counted or affected by this condition.
858 
859     // SortedNumberOf does not have a valid local candidate to be matched
860     // before the subcondition is evaluated, so the local context that is
861     // passed to the subcondition should have a null local candidate.
862     std::shared_ptr<const UniverseObject> no_object;
863     ScriptingContext local_context(parent_context, no_object);
864 
865     // which input matches match the subcondition?
866     ObjectSet subcondition_matching_matches;
867     subcondition_matching_matches.reserve(matches.size());
868     m_condition->Eval(local_context, subcondition_matching_matches, matches, NON_MATCHES);
869 
870     // remaining input matches don't match the subcondition...
871     ObjectSet subcondition_non_matching_matches = matches;
872     matches.clear();    // to be refilled later
873 
874     // which input non_matches match the subcondition?
875     ObjectSet subcondition_matching_non_matches;
876     subcondition_matching_non_matches.reserve(non_matches.size());
877     m_condition->Eval(local_context, subcondition_matching_non_matches, non_matches, NON_MATCHES);
878 
879     // remaining input non_matches don't match the subcondition...
880     ObjectSet subcondition_non_matching_non_matches = non_matches;
881     non_matches.clear();    // to be refilled later
882 
883     // assemble single set of subcondition matching objects
884     ObjectSet all_subcondition_matches;
885     all_subcondition_matches.reserve(subcondition_matching_matches.size() + subcondition_matching_non_matches.size());
886     all_subcondition_matches.insert(all_subcondition_matches.end(), subcondition_matching_matches.begin(), subcondition_matching_matches.end());
887     all_subcondition_matches.insert(all_subcondition_matches.end(), subcondition_matching_non_matches.begin(), subcondition_matching_non_matches.end());
888 
889     // how many subcondition matches to select as matches to this condition
890     int number = m_number->Eval(local_context);
891 
892     // compile single set of all objects that are matched by this condition.
893     // these are the objects that should be transferred from non_matches into
894     // matches, or those left in matches while the rest are moved into non_matches
895     ObjectSet matched_objects;
896     matched_objects.reserve(number);
897     TransferSortedObjects(number, m_sort_key.get(), parent_context, m_sorting_method, all_subcondition_matches, matched_objects);
898 
899     // put objects back into matches and non_target sets as output...
900 
901     if (search_domain == NON_MATCHES) {
902         // put matched objects that are in subcondition_matching_non_matches into matches
903         for (auto& matched_object : matched_objects) {
904             // is this matched object in subcondition_matching_non_matches?
905             auto smnt_it = std::find(subcondition_matching_non_matches.begin(),
906                                      subcondition_matching_non_matches.end(), matched_object);
907             if (smnt_it != subcondition_matching_non_matches.end()) {
908                 // yes; move object to matches
909                 *smnt_it = subcondition_matching_non_matches.back();
910                 subcondition_matching_non_matches.pop_back();
911                 matches.push_back(matched_object);
912             }
913         }
914 
915         // put remaining (non-matched) objects in subcondition_matching_non_matches back into non_matches
916         non_matches.insert( non_matches.end(), subcondition_matching_non_matches.begin(),      subcondition_matching_non_matches.end());
917         // put objects in subcondition_non_matching_non_matches back into non_matches
918         non_matches.insert( non_matches.end(), subcondition_non_matching_non_matches.begin(),  subcondition_non_matching_non_matches.end());
919         // put objects in subcondition_matching_matches and subcondition_non_matching_matches back into matches
920         matches.insert(     matches.end(),     subcondition_matching_matches.begin(),          subcondition_matching_matches.end());
921         matches.insert(     matches.end(),     subcondition_non_matching_matches.begin(),      subcondition_non_matching_matches.end());
922         // this leaves the original contents of matches unchanged, other than
923         // possibly having transferred some objects into matches from non_matches
924 
925     } else { /*(search_domain == MATCHES)*/
926         // put matched objecs that are in subcondition_matching_matches back into matches
927         for (auto& matched_object : matched_objects) {
928             // is this matched object in subcondition_matching_matches?
929             auto smt_it = std::find(subcondition_matching_matches.begin(),
930                                     subcondition_matching_matches.end(), matched_object);
931             if (smt_it != subcondition_matching_matches.end()) {
932                 // yes; move back into matches
933                 *smt_it = subcondition_matching_matches.back();
934                 subcondition_matching_matches.pop_back();
935                 matches.push_back(matched_object);
936             }
937         }
938 
939         // put remaining (non-matched) objects in subcondition_matching_matches) into non_matches
940         non_matches.insert( non_matches.end(), subcondition_matching_matches.begin(),          subcondition_matching_matches.end());
941         // put objects in subcondition_non_matching_matches into non_matches
942         non_matches.insert( non_matches.end(), subcondition_non_matching_matches.begin(),      subcondition_non_matching_matches.end());
943         // put objects in subcondition_matching_non_matches and subcondition_non_matching_non_matches back into non_matches
944         non_matches.insert( non_matches.end(), subcondition_matching_non_matches.begin(),      subcondition_matching_non_matches.end());
945         non_matches.insert( non_matches.end(), subcondition_non_matching_non_matches.begin(),  subcondition_non_matching_non_matches.end());
946         // this leaves the original contents of non_matches unchanged, other than
947         // possibly having transferred some objects into non_matches from matches
948     }
949 }
950 
Description(bool negated) const951 std::string SortedNumberOf::Description(bool negated/* = false*/) const {
952     std::string number_str = m_number->ConstantExpr() ? m_number->Dump() : m_number->Description();
953 
954     if (m_sorting_method == SORT_RANDOM) {
955         return str(FlexibleFormat((!negated)
956                                   ? UserString("DESC_NUMBER_OF")
957                                   : UserString("DESC_NUMBER_OF_NOT")
958                                  )
959                    % number_str
960                    % m_condition->Description());
961     } else {
962         std::string sort_key_str = m_sort_key->ConstantExpr() ? m_sort_key->Dump() : m_sort_key->Description();
963 
964         std::string description_str;
965         switch (m_sorting_method) {
966         case SORT_MAX:
967             description_str = (!negated)
968                 ? UserString("DESC_MAX_NUMBER_OF")
969                 : UserString("DESC_MAX_NUMBER_OF_NOT");
970             break;
971 
972         case SORT_MIN:
973             description_str = (!negated)
974                 ? UserString("DESC_MIN_NUMBER_OF")
975                 : UserString("DESC_MIN_NUMBER_OF_NOT");
976             break;
977 
978         case SORT_MODE:
979             description_str = (!negated)
980                 ? UserString("DESC_MODE_NUMBER_OF")
981                 : UserString("DESC_MODE_NUMBER_OF_NOT");
982             break;
983         default:
984             break;
985         }
986 
987         return str(FlexibleFormat(description_str)
988                    % number_str
989                    % sort_key_str
990                    % m_condition->Description());
991     }
992 }
993 
Dump(unsigned short ntabs) const994 std::string SortedNumberOf::Dump(unsigned short ntabs) const {
995     std::string retval = DumpIndent(ntabs);
996     switch (m_sorting_method) {
997     case SORT_RANDOM:
998         retval += "NumberOf";   break;
999     case SORT_MAX:
1000         retval += "MaximumNumberOf";  break;
1001     case SORT_MIN:
1002         retval += "MinimumNumberOf"; break;
1003     case SORT_MODE:
1004         retval += "ModeNumberOf"; break;
1005     default:
1006         retval += "??NumberOf??"; break;
1007     }
1008 
1009     retval += " number = " + m_number->Dump(ntabs);
1010 
1011     if (m_sort_key)
1012          retval += " sortby = " + m_sort_key->Dump(ntabs);
1013 
1014     retval += " condition =\n";
1015     retval += m_condition->Dump(ntabs+1);
1016 
1017     return retval;
1018 }
1019 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const1020 void SortedNumberOf::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
1021                                                        ObjectSet& condition_non_targets) const
1022 {
1023     if (m_condition) {
1024         m_condition->GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
1025     } else {
1026         Condition::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
1027     }
1028 }
1029 
SetTopLevelContent(const std::string & content_name)1030 void SortedNumberOf::SetTopLevelContent(const std::string& content_name) {
1031     if (m_number)
1032         m_number->SetTopLevelContent(content_name);
1033     if (m_sort_key)
1034         m_sort_key->SetTopLevelContent(content_name);
1035     if (m_condition)
1036         m_condition->SetTopLevelContent(content_name);
1037 }
1038 
GetCheckSum() const1039 unsigned int SortedNumberOf::GetCheckSum() const {
1040     unsigned int retval{0};
1041 
1042     CheckSums::CheckSumCombine(retval, "Condition::SortedNumberOf");
1043     CheckSums::CheckSumCombine(retval, m_number);
1044     CheckSums::CheckSumCombine(retval, m_sort_key);
1045     CheckSums::CheckSumCombine(retval, m_sorting_method);
1046     CheckSums::CheckSumCombine(retval, m_condition);
1047 
1048     TraceLogger() << "GetCheckSum(SortedNumberOf): retval: " << retval;
1049     return retval;
1050 }
1051 
1052 ///////////////////////////////////////////////////////////
1053 // All                                                   //
1054 ///////////////////////////////////////////////////////////
All()1055 All::All() :
1056     Condition()
1057 {
1058     m_root_candidate_invariant = true;
1059     m_target_invariant = true;
1060     m_source_invariant = true;
1061 }
1062 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const1063 void All::Eval(const ScriptingContext& parent_context,
1064                           ObjectSet& matches, ObjectSet& non_matches,
1065                           SearchDomain search_domain/* = NON_MATCHES*/) const
1066 {
1067     if (search_domain == NON_MATCHES) {
1068         // move all objects from non_matches to matches
1069         matches.insert(matches.end(), non_matches.begin(), non_matches.end());
1070         non_matches.clear();
1071     }
1072     // if search_comain is MATCHES, do nothing: all objects in matches set
1073     // match this condition, so should remain in matches set
1074 }
1075 
operator ==(const Condition & rhs) const1076 bool All::operator==(const Condition& rhs) const
1077 { return Condition::operator==(rhs); }
1078 
Description(bool negated) const1079 std::string All::Description(bool negated/* = false*/) const {
1080     return (!negated)
1081         ? UserString("DESC_ALL")
1082         : UserString("DESC_ALL_NOT");
1083 }
1084 
Dump(unsigned short ntabs) const1085 std::string All::Dump(unsigned short ntabs) const
1086 { return DumpIndent(ntabs) + "All\n"; }
1087 
GetCheckSum() const1088 unsigned int All::GetCheckSum() const {
1089     unsigned int retval{0};
1090 
1091     CheckSums::CheckSumCombine(retval, "Condition::All");
1092 
1093     TraceLogger() << "GetCheckSum(All): retval: " << retval;
1094     return retval;
1095 }
1096 
1097 ///////////////////////////////////////////////////////////
1098 // None                                                  //
1099 ///////////////////////////////////////////////////////////
None()1100 None::None() :
1101     Condition()
1102 {
1103     m_root_candidate_invariant = true;
1104     m_target_invariant = true;
1105     m_source_invariant = true;
1106 }
1107 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const1108 void None::Eval(const ScriptingContext& parent_context,
1109                 ObjectSet& matches, ObjectSet& non_matches,
1110                 SearchDomain search_domain/* = NON_MATCHES*/) const
1111 {
1112     if (search_domain == MATCHES) {
1113         // move all objects from matches to non_matches
1114         non_matches.insert(non_matches.end(), matches.begin(), matches.end());
1115         matches.clear();
1116     }
1117     // if search domain is non_matches, no need to do anything since none of them match None.
1118 }
1119 
operator ==(const Condition & rhs) const1120 bool None::operator==(const Condition& rhs) const
1121 { return Condition::operator==(rhs); }
1122 
Description(bool negated) const1123 std::string None::Description(bool negated/* = false*/) const {
1124     return (!negated)
1125         ? UserString("DESC_NONE")
1126         : UserString("DESC_NONE_NOT");
1127 }
1128 
Dump(unsigned short ntabs) const1129 std::string None::Dump(unsigned short ntabs) const
1130 { return DumpIndent(ntabs) + "None\n"; }
1131 
GetCheckSum() const1132 unsigned int None::GetCheckSum() const {
1133     unsigned int retval{0};
1134 
1135     CheckSums::CheckSumCombine(retval, "Condition::None");
1136 
1137     TraceLogger() << "GetCheckSum(None): retval: " << retval;
1138     return retval;
1139 }
1140 
1141 ///////////////////////////////////////////////////////////
1142 // EmpireAffiliation                                     //
1143 ///////////////////////////////////////////////////////////
EmpireAffiliation(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,EmpireAffiliationType affiliation)1144 EmpireAffiliation::EmpireAffiliation(std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
1145                                      EmpireAffiliationType affiliation) :
1146     m_empire_id(std::move(empire_id)),
1147     m_affiliation(affiliation)
1148 {
1149     m_root_candidate_invariant = !m_empire_id || m_empire_id->RootCandidateInvariant();
1150     m_target_invariant = !m_empire_id || m_empire_id->TargetInvariant();
1151     m_source_invariant = !m_empire_id || m_empire_id->SourceInvariant();
1152 }
1153 
EmpireAffiliation(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id)1154 EmpireAffiliation::EmpireAffiliation(std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id) :
1155     EmpireAffiliation(std::move(empire_id), AFFIL_SELF)
1156 {}
1157 
EmpireAffiliation(EmpireAffiliationType affiliation)1158 EmpireAffiliation::EmpireAffiliation(EmpireAffiliationType affiliation) :
1159     EmpireAffiliation(nullptr, affiliation)
1160 {}
1161 
operator ==(const Condition & rhs) const1162 bool EmpireAffiliation::operator==(const Condition& rhs) const {
1163     if (this == &rhs)
1164         return true;
1165     if (typeid(*this) != typeid(rhs))
1166         return false;
1167 
1168     const EmpireAffiliation& rhs_ = static_cast<const EmpireAffiliation&>(rhs);
1169 
1170     if (m_affiliation != rhs_.m_affiliation)
1171         return false;
1172 
1173     CHECK_COND_VREF_MEMBER(m_empire_id)
1174 
1175     return true;
1176 }
1177 
1178 namespace {
1179     struct EmpireAffiliationSimpleMatch {
EmpireAffiliationSimpleMatchCondition::__anone9a56faf0911::EmpireAffiliationSimpleMatch1180         EmpireAffiliationSimpleMatch(int empire_id, EmpireAffiliationType affiliation) :
1181             m_empire_id(empire_id),
1182             m_affiliation(affiliation)
1183         {}
1184 
operator ()Condition::__anone9a56faf0911::EmpireAffiliationSimpleMatch1185         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
1186             if (!candidate)
1187                 return false;
1188 
1189             switch (m_affiliation) {
1190             case AFFIL_SELF:
1191                 return m_empire_id != ALL_EMPIRES && candidate->OwnedBy(m_empire_id);
1192                 break;
1193 
1194             case AFFIL_ENEMY: {
1195                 if (m_empire_id == ALL_EMPIRES)
1196                     return true;
1197                 if (m_empire_id == candidate->Owner())
1198                     return false;
1199                 DiplomaticStatus status = Empires().GetDiplomaticStatus(m_empire_id, candidate->Owner());
1200                 return (status == DIPLO_WAR);
1201                 break;
1202             }
1203 
1204             case AFFIL_PEACE: {
1205                 if (m_empire_id == ALL_EMPIRES)
1206                     return false;
1207                 if (m_empire_id == candidate->Owner())
1208                     return false;
1209                 DiplomaticStatus status = Empires().GetDiplomaticStatus(m_empire_id, candidate->Owner());
1210                 return (status == DIPLO_PEACE);
1211                 break;
1212             }
1213 
1214             case AFFIL_ALLY: {
1215                 if (m_empire_id == ALL_EMPIRES)
1216                     return false;
1217                 if (m_empire_id == candidate->Owner())
1218                     return false;
1219                 DiplomaticStatus status = Empires().GetDiplomaticStatus(m_empire_id, candidate->Owner());
1220                 return (status >= DIPLO_ALLIED);
1221                 break;
1222             }
1223 
1224             case AFFIL_ANY:
1225                 return !candidate->Unowned();
1226                 break;
1227 
1228             case AFFIL_NONE:
1229                 return candidate->Unowned();
1230                 break;
1231 
1232             case AFFIL_CAN_SEE: {
1233                 // todo...
1234                 return false;
1235                 break;
1236             }
1237 
1238             case AFFIL_HUMAN: {
1239                 if (candidate->Unowned())
1240                     return false;
1241                 if (GetEmpireClientType(candidate->Owner()) == Networking::CLIENT_TYPE_HUMAN_PLAYER)
1242                     return true;
1243                 return false;
1244                 break;
1245             }
1246 
1247             default:
1248                 return false;
1249                 break;
1250             }
1251         }
1252 
1253         int m_empire_id;
1254         EmpireAffiliationType m_affiliation;
1255     };
1256 }
1257 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const1258 void EmpireAffiliation::Eval(const ScriptingContext& parent_context,
1259                              ObjectSet& matches, ObjectSet& non_matches,
1260                              SearchDomain search_domain/* = NON_MATCHES*/) const
1261 {
1262     bool simple_eval_safe = (!m_empire_id || m_empire_id->ConstantExpr()) ||
1263                             ((!m_empire_id || m_empire_id->LocalCandidateInvariant()) &&
1264                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
1265     if (simple_eval_safe) {
1266         // evaluate empire id once, and use to check all candidate objects
1267         int empire_id = m_empire_id ? m_empire_id->Eval(parent_context) : ALL_EMPIRES;
1268         EvalImpl(matches, non_matches, search_domain, EmpireAffiliationSimpleMatch(empire_id, m_affiliation));
1269     } else {
1270         // re-evaluate empire id for each candidate object
1271         Condition::Eval(parent_context, matches, non_matches, search_domain);
1272     }
1273 }
1274 
Description(bool negated) const1275 std::string EmpireAffiliation::Description(bool negated/* = false*/) const {
1276     std::string empire_str;
1277     if (m_empire_id) {
1278         int empire_id = ALL_EMPIRES;
1279         if (m_empire_id->ConstantExpr())
1280             empire_id = m_empire_id->Eval();
1281         if (const Empire* empire = GetEmpire(empire_id))
1282             empire_str = empire->Name();
1283         else
1284             empire_str = m_empire_id->Description();
1285     }
1286 
1287     if (m_affiliation == AFFIL_SELF) {
1288         return str(FlexibleFormat((!negated)
1289             ? UserString("DESC_EMPIRE_AFFILIATION_SELF")
1290             : UserString("DESC_EMPIRE_AFFILIATION_SELF_NOT")) % empire_str);
1291     } else if (m_affiliation == AFFIL_ANY) {
1292         return (!negated)
1293             ? UserString("DESC_EMPIRE_AFFILIATION_ANY")
1294             : UserString("DESC_EMPIRE_AFFILIATION_ANY_NOT");
1295     } else if (m_affiliation == AFFIL_NONE) {
1296         return (!negated)
1297             ? UserString("DESC_EMPIRE_AFFILIATION_ANY_NOT")
1298             : UserString("DESC_EMPIRE_AFFILIATION_ANY");
1299     } else {
1300         return str(FlexibleFormat((!negated)
1301             ? UserString("DESC_EMPIRE_AFFILIATION")
1302             : UserString("DESC_EMPIRE_AFFILIATION_NOT"))
1303                    % UserString(boost::lexical_cast<std::string>(m_affiliation))
1304                    % empire_str);
1305     }
1306 }
1307 
Dump(unsigned short ntabs) const1308 std::string EmpireAffiliation::Dump(unsigned short ntabs) const {
1309     std::string retval = DumpIndent(ntabs);
1310     if (m_affiliation == AFFIL_SELF) {
1311         retval += "OwnedBy";
1312         if (m_empire_id)
1313             retval += " empire = " + m_empire_id->Dump(ntabs);
1314 
1315     } else if (m_affiliation == AFFIL_ANY) {
1316         retval += "OwnedBy affiliation = AnyEmpire";
1317 
1318     } else if (m_affiliation == AFFIL_NONE) {
1319         retval += "Unowned";
1320 
1321     } else if (m_affiliation == AFFIL_ENEMY) {
1322         retval += "OwnedBy affiliation = EnemyOf";
1323         if (m_empire_id)
1324             retval += " empire = " + m_empire_id->Dump(ntabs);
1325 
1326     } else if (m_affiliation == AFFIL_PEACE) {
1327         retval += "OwnedBy affiliation = PeaceWith";
1328         if (m_empire_id)
1329             retval += " empire = " + m_empire_id->Dump(ntabs);
1330 
1331     } else if (m_affiliation == AFFIL_ALLY) {
1332         retval += "OwnedBy affiliation = AllyOf";
1333         if (m_empire_id)
1334             retval += " empire = " + m_empire_id->Dump(ntabs);
1335 
1336     } else if (m_affiliation == AFFIL_CAN_SEE) {
1337         retval += "OwnedBy affiliation = CanSee";
1338 
1339     } else if (m_affiliation == AFFIL_HUMAN) {
1340         retval += "OwnedBy affiliation = Human";
1341 
1342     } else {
1343         retval += "OwnedBy ??";
1344     }
1345 
1346     retval += "\n";
1347     return retval;
1348 }
1349 
Match(const ScriptingContext & local_context) const1350 bool EmpireAffiliation::Match(const ScriptingContext& local_context) const {
1351     auto candidate = local_context.condition_local_candidate;
1352     if (!candidate) {
1353         ErrorLogger() << "EmpireAffiliation::Match passed no candidate object";
1354         return false;
1355     }
1356 
1357     int empire_id = m_empire_id ? m_empire_id->Eval(local_context) : ALL_EMPIRES;
1358 
1359     return EmpireAffiliationSimpleMatch(empire_id, m_affiliation)(candidate);
1360 }
1361 
SetTopLevelContent(const std::string & content_name)1362 void EmpireAffiliation::SetTopLevelContent(const std::string& content_name) {
1363     if (m_empire_id)
1364         m_empire_id->SetTopLevelContent(content_name);
1365 }
1366 
GetCheckSum() const1367 unsigned int EmpireAffiliation::GetCheckSum() const {
1368     unsigned int retval{0};
1369 
1370     CheckSums::CheckSumCombine(retval, "Condition::EmpireAffiliation");
1371     CheckSums::CheckSumCombine(retval, m_empire_id);
1372     CheckSums::CheckSumCombine(retval, m_affiliation);
1373 
1374     TraceLogger() << "GetCheckSum(EmpireAffiliation): retval: " << retval;
1375     return retval;
1376 }
1377 
1378 ///////////////////////////////////////////////////////////
1379 // Source                                                //
1380 ///////////////////////////////////////////////////////////
Source()1381 Source::Source() :
1382     Condition()
1383 {
1384     m_root_candidate_invariant = true;
1385     m_target_invariant = true;
1386     m_source_invariant = false;
1387 }
1388 
operator ==(const Condition & rhs) const1389 bool Source::operator==(const Condition& rhs) const
1390 { return Condition::operator==(rhs); }
1391 
Description(bool negated) const1392 std::string Source::Description(bool negated/* = false*/) const {
1393     return (!negated)
1394         ? UserString("DESC_SOURCE")
1395         : UserString("DESC_SOURCE_NOT");
1396 }
1397 
Dump(unsigned short ntabs) const1398 std::string Source::Dump(unsigned short ntabs) const
1399 { return DumpIndent(ntabs) + "Source\n"; }
1400 
Match(const ScriptingContext & local_context) const1401 bool Source::Match(const ScriptingContext& local_context) const {
1402     if (!local_context.source)
1403         return false;
1404     return local_context.source == local_context.condition_local_candidate;
1405 }
1406 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const1407 void Source::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
1408                                                ObjectSet& condition_non_targets) const
1409 {
1410     if (parent_context.source)
1411         condition_non_targets.push_back(parent_context.source);
1412 }
1413 
GetCheckSum() const1414 unsigned int Source::GetCheckSum() const {
1415     unsigned int retval{0};
1416 
1417     CheckSums::CheckSumCombine(retval, "Condition::Source");
1418 
1419     TraceLogger() << "GetCheckSum(Source): retval: " << retval;
1420     return retval;
1421 }
1422 
1423 ///////////////////////////////////////////////////////////
1424 // RootCandidate                                         //
1425 ///////////////////////////////////////////////////////////
RootCandidate()1426 RootCandidate::RootCandidate() :
1427     Condition()
1428 {
1429     m_root_candidate_invariant = false;
1430     m_target_invariant = true;
1431     m_source_invariant = true;
1432 }
1433 
operator ==(const Condition & rhs) const1434 bool RootCandidate::operator==(const Condition& rhs) const
1435 { return Condition::operator==(rhs); }
1436 
Description(bool negated) const1437 std::string RootCandidate::Description(bool negated/* = false*/) const {
1438     return (!negated)
1439         ? UserString("DESC_ROOT_CANDIDATE")
1440         : UserString("DESC_ROOT_CANDIDATE_NOT");
1441 }
1442 
Dump(unsigned short ntabs) const1443 std::string RootCandidate::Dump(unsigned short ntabs) const
1444 { return DumpIndent(ntabs) + "RootCandidate\n"; }
1445 
Match(const ScriptingContext & local_context) const1446 bool RootCandidate::Match(const ScriptingContext& local_context) const {
1447     if (!local_context.condition_root_candidate)
1448         return false;
1449     return local_context.condition_root_candidate == local_context.condition_local_candidate;
1450 }
1451 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const1452 void RootCandidate::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
1453                                                       ObjectSet& condition_non_targets) const
1454 {
1455     if (parent_context.condition_root_candidate)
1456         condition_non_targets.push_back(parent_context.condition_root_candidate);
1457 }
1458 
GetCheckSum() const1459 unsigned int RootCandidate::GetCheckSum() const {
1460     unsigned int retval{0};
1461 
1462     CheckSums::CheckSumCombine(retval, "Condition::RootCandidate");
1463 
1464     TraceLogger() << "GetCheckSum(RootCandidate): retval: " << retval;
1465     return retval;
1466 }
1467 
1468 ///////////////////////////////////////////////////////////
1469 // Target                                                //
1470 ///////////////////////////////////////////////////////////
Target()1471 Target::Target() :
1472     Condition()
1473 {
1474     m_root_candidate_invariant = true;
1475     m_target_invariant = false;
1476     m_source_invariant = true;
1477 }
1478 
operator ==(const Condition & rhs) const1479 bool Target::operator==(const Condition& rhs) const
1480 { return Condition::operator==(rhs); }
1481 
Description(bool negated) const1482 std::string Target::Description(bool negated/* = false*/) const {
1483     return (!negated)
1484         ? UserString("DESC_TARGET")
1485         : UserString("DESC_TARGET_NOT");
1486 }
1487 
Dump(unsigned short ntabs) const1488 std::string Target::Dump(unsigned short ntabs) const
1489 { return DumpIndent(ntabs) + "Target\n"; }
1490 
Match(const ScriptingContext & local_context) const1491 bool Target::Match(const ScriptingContext& local_context) const {
1492     if (!local_context.effect_target)
1493         return false;
1494     return local_context.effect_target == local_context.condition_local_candidate;
1495 }
1496 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const1497 void Target::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
1498                                                ObjectSet& condition_non_targets) const
1499 {
1500     if (parent_context.effect_target)
1501         condition_non_targets.push_back(parent_context.effect_target);
1502 }
1503 
GetCheckSum() const1504 unsigned int Target::GetCheckSum() const {
1505     unsigned int retval{0};
1506 
1507     CheckSums::CheckSumCombine(retval, "Condition::Target");
1508 
1509     TraceLogger() << "GetCheckSum(Target): retval: " << retval;
1510     return retval;
1511 }
1512 
1513 ///////////////////////////////////////////////////////////
1514 // Homeworld                                             //
1515 ///////////////////////////////////////////////////////////
Homeworld()1516 Homeworld::Homeworld() :
1517     Homeworld(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>>{})
1518 {}
1519 
Homeworld(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>> && names)1520 Homeworld::Homeworld(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>>&& names) :
1521     Condition(),
1522     m_names(std::move(names))
1523 {
1524     m_root_candidate_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->RootCandidateInvariant(); });
1525     m_target_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->TargetInvariant(); });
1526     m_source_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->SourceInvariant(); });
1527 }
1528 
operator ==(const Condition & rhs) const1529 bool Homeworld::operator==(const Condition& rhs) const {
1530     if (this == &rhs)
1531         return true;
1532     if (typeid(*this) != typeid(rhs))
1533         return false;
1534 
1535     const Homeworld& rhs_ = static_cast<const Homeworld&>(rhs);
1536 
1537     if (m_names.size() != rhs_.m_names.size())
1538         return false;
1539     for (unsigned int i = 0; i < m_names.size(); ++i) {
1540         CHECK_COND_VREF_MEMBER(m_names.at(i))
1541     }
1542 
1543     return true;
1544 }
1545 
1546 namespace {
1547     struct HomeworldSimpleMatch {
HomeworldSimpleMatchCondition::__anone9a56faf0d11::HomeworldSimpleMatch1548         HomeworldSimpleMatch(const std::vector<std::string>& names, const ObjectMap& objects) :
1549             m_names(names),
1550             m_objects(objects)
1551         {}
1552 
operator ()Condition::__anone9a56faf0d11::HomeworldSimpleMatch1553         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
1554             if (!candidate)
1555                 return false;
1556 
1557             // is it a planet or a building on a planet?
1558             auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
1559             std::shared_ptr<const ::Building> building;
1560             if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
1561                 planet = m_objects.get<Planet>(building->PlanetID());
1562             if (!planet)
1563                 return false;
1564 
1565             int planet_id = planet->ID();
1566             const SpeciesManager& manager = GetSpeciesManager();
1567 
1568             if (m_names.empty()) {
1569                 // match homeworlds for any species
1570                 for (auto species_it = manager.begin(); species_it != manager.end(); ++species_it) {
1571                     if (const auto& species = species_it->second) {
1572                         const auto& homeworld_ids = species->Homeworlds();
1573                         if (homeworld_ids.count(planet_id))
1574                             return true;
1575                     }
1576                 }
1577 
1578             } else {
1579                 // match any of the species specified
1580                 for (const std::string& name : m_names) {
1581                     if (auto species = GetSpecies(name)) {
1582                         const auto& homeworld_ids = species->Homeworlds();
1583                         if (homeworld_ids.count(planet_id))
1584                             return true;
1585                     }
1586                 }
1587             }
1588 
1589             return false;
1590         }
1591 
1592         const std::vector<std::string>& m_names;
1593         const ObjectMap& m_objects;
1594     };
1595 }
1596 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const1597 void Homeworld::Eval(const ScriptingContext& parent_context,
1598                      ObjectSet& matches, ObjectSet& non_matches,
1599                      SearchDomain search_domain/* = NON_MATCHES*/) const
1600 {
1601     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
1602     if (simple_eval_safe) {
1603         // check each valueref for invariance to local candidate
1604         for (auto& name : m_names) {
1605             if (!name->LocalCandidateInvariant()) {
1606                 simple_eval_safe = false;
1607                 break;
1608             }
1609         }
1610     }
1611     if (simple_eval_safe) {
1612         // evaluate names once, and use to check all candidate objects
1613         std::vector<std::string> names;
1614         names.reserve(m_names.size());
1615         // get all names from valuerefs
1616         for (auto& name : m_names)
1617             names.push_back(name->Eval(parent_context));
1618         EvalImpl(matches, non_matches, search_domain, HomeworldSimpleMatch(names, parent_context.ContextObjects()));
1619     } else {
1620         // re-evaluate allowed names for each candidate object
1621         Condition::Eval(parent_context, matches, non_matches, search_domain);
1622     }
1623 }
1624 
Description(bool negated) const1625 std::string Homeworld::Description(bool negated/* = false*/) const {
1626     std::string values_str;
1627     for (unsigned int i = 0; i < m_names.size(); ++i) {
1628         values_str += m_names[i]->ConstantExpr() ?
1629                         UserString(m_names[i]->Eval()) :
1630                         m_names[i]->Description();
1631         if (2 <= m_names.size() && i < m_names.size() - 2) {
1632             values_str += ", ";
1633         } else if (i == m_names.size() - 2) {
1634             values_str += m_names.size() < 3 ? " " : ", ";
1635             values_str += UserString("OR");
1636             values_str += " ";
1637         }
1638     }
1639     return str(FlexibleFormat((!negated)
1640         ? UserString("DESC_HOMEWORLD")
1641         : UserString("DESC_HOMEWORLD_NOT"))
1642         % values_str);
1643 }
1644 
Dump(unsigned short ntabs) const1645 std::string Homeworld::Dump(unsigned short ntabs) const {
1646     std::string retval = DumpIndent(ntabs) + "HomeWorld";
1647     if (m_names.size() == 1) {
1648         retval += " name = " + m_names[0]->Dump(ntabs);
1649     } else if (!m_names.empty()) {
1650         retval += " name = [ ";
1651         for (auto& name : m_names) {
1652             retval += name->Dump(ntabs) + " ";
1653         }
1654         retval += "]";
1655     }
1656     return retval;
1657 }
1658 
Match(const ScriptingContext & local_context) const1659 bool Homeworld::Match(const ScriptingContext& local_context) const {
1660     auto candidate = local_context.condition_local_candidate;
1661     if (!candidate) {
1662         ErrorLogger() << "Homeworld::Match passed no candidate object";
1663         return false;
1664     }
1665 
1666     // is it a planet or a building on a planet?
1667     auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
1668     std::shared_ptr<const ::Building> building;
1669     if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
1670         planet = local_context.ContextObjects().get<Planet>(building->PlanetID());
1671     if (!planet)
1672         return false;
1673 
1674     int planet_id = planet->ID();
1675     const SpeciesManager& manager = GetSpeciesManager();
1676 
1677     if (m_names.empty()) {
1678         // match homeworlds for any species
1679         for (const auto& entry : manager) {
1680             if (const auto& species = entry.second) {
1681                 const auto& homeworld_ids = species->Homeworlds();
1682                 if (homeworld_ids.count(planet_id))
1683                     return true;
1684             }
1685         }
1686 
1687     } else {
1688         // match any of the species specified
1689         for (auto& name : m_names) {
1690             const auto& species_name = name->Eval(local_context);
1691             if (const auto species = manager.GetSpecies(species_name)) {
1692                 const auto& homeworld_ids = species->Homeworlds();
1693                 if (homeworld_ids.count(planet_id))
1694                     return true;
1695             }
1696         }
1697     }
1698 
1699     return false;
1700 }
1701 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const1702 void Homeworld::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
1703                                                   ObjectSet& condition_non_targets) const
1704 { AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); }
1705 
SetTopLevelContent(const std::string & content_name)1706 void Homeworld::SetTopLevelContent(const std::string& content_name) {
1707     for (auto& name : m_names) {
1708         if (name)
1709             name->SetTopLevelContent(content_name);
1710     }
1711 }
1712 
GetCheckSum() const1713 unsigned int Homeworld::GetCheckSum() const {
1714     unsigned int retval{0};
1715 
1716     CheckSums::CheckSumCombine(retval, "Condition::Homeworld");
1717     CheckSums::CheckSumCombine(retval, m_names);
1718 
1719     TraceLogger() << "GetCheckSum(Homeworld): retval: " << retval;
1720     return retval;
1721 }
1722 
1723 ///////////////////////////////////////////////////////////
1724 // Capital                                               //
1725 ///////////////////////////////////////////////////////////
Capital()1726 Capital::Capital() :
1727     Condition()
1728 {
1729     m_root_candidate_invariant = true;
1730     m_target_invariant = true;
1731     m_source_invariant = true;
1732 }
1733 
operator ==(const Condition & rhs) const1734 bool Capital::operator==(const Condition& rhs) const
1735 { return Condition::operator==(rhs); }
1736 
Description(bool negated) const1737 std::string Capital::Description(bool negated/* = false*/) const {
1738     return (!negated)
1739         ? UserString("DESC_CAPITAL")
1740         : UserString("DESC_CAPITAL_NOT");
1741 }
1742 
Dump(unsigned short ntabs) const1743 std::string Capital::Dump(unsigned short ntabs) const
1744 { return DumpIndent(ntabs) + "Capital\n"; }
1745 
Match(const ScriptingContext & local_context) const1746 bool Capital::Match(const ScriptingContext& local_context) const {
1747     auto candidate = local_context.condition_local_candidate;
1748     if (!candidate) {
1749         ErrorLogger() << "Capital::Match passed no candidate object";
1750         return false;
1751     }
1752     int candidate_id = candidate->ID();
1753 
1754     // check if any empire's capital's ID is that candidate object's id.
1755     // if it is, the candidate object is a capital.
1756     for (const auto& entry : Empires())
1757         if (entry.second->CapitalID() == candidate_id)
1758             return true;
1759     return false;
1760 }
1761 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const1762 void Capital::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
1763                                                 ObjectSet& condition_non_targets) const
1764 { AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); }
1765 
GetCheckSum() const1766 unsigned int Capital::GetCheckSum() const {
1767     unsigned int retval{0};
1768 
1769     CheckSums::CheckSumCombine(retval, "Condition::Capital");
1770 
1771     TraceLogger() << "GetCheckSum(Capital): retval: " << retval;
1772     return retval;
1773 }
1774 
1775 ///////////////////////////////////////////////////////////
1776 // Monster                                               //
1777 ///////////////////////////////////////////////////////////
Monster()1778 Monster::Monster() :
1779     Condition()
1780 {
1781     m_root_candidate_invariant = true;
1782     m_target_invariant = true;
1783     m_source_invariant = true;
1784 }
1785 
operator ==(const Condition & rhs) const1786 bool Monster::operator==(const Condition& rhs) const
1787 { return Condition::operator==(rhs); }
1788 
Description(bool negated) const1789 std::string Monster::Description(bool negated/* = false*/) const {
1790     return (!negated)
1791         ? UserString("DESC_MONSTER")
1792         : UserString("DESC_MONSTER_NOT");
1793 }
1794 
Dump(unsigned short ntabs) const1795 std::string Monster::Dump(unsigned short ntabs) const
1796 { return DumpIndent(ntabs) + "Monster\n"; }
1797 
Match(const ScriptingContext & local_context) const1798 bool Monster::Match(const ScriptingContext& local_context) const {
1799     auto candidate = local_context.condition_local_candidate;
1800     if (!candidate) {
1801         ErrorLogger() << "Monster::Match passed no candidate object";
1802         return false;
1803     }
1804 
1805     if (auto ship = std::dynamic_pointer_cast<const Ship>(candidate))
1806         if (ship->IsMonster())
1807             return true;
1808 
1809     return false;
1810 }
1811 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const1812 void Monster::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
1813                                                 ObjectSet& condition_non_targets) const
1814 { AddShipSet(parent_context.ContextObjects(), condition_non_targets); }
1815 
GetCheckSum() const1816 unsigned int Monster::GetCheckSum() const {
1817     unsigned int retval{0};
1818 
1819     CheckSums::CheckSumCombine(retval, "Condition::Monster");
1820 
1821     TraceLogger() << "GetCheckSum(Monster): retval: " << retval;
1822     return retval;
1823 }
1824 
1825 ///////////////////////////////////////////////////////////
1826 // Armed                                                 //
1827 ///////////////////////////////////////////////////////////
Armed()1828 Armed::Armed() :
1829     Condition()
1830 {
1831     m_root_candidate_invariant = true;
1832     m_target_invariant = true;
1833     m_source_invariant = true;
1834 }
1835 
operator ==(const Condition & rhs) const1836 bool Armed::operator==(const Condition& rhs) const
1837 { return Condition::operator==(rhs); }
1838 
Description(bool negated) const1839 std::string Armed::Description(bool negated/* = false*/) const {
1840     return (!negated)
1841         ? UserString("DESC_ARMED")
1842         : UserString("DESC_ARMED_NOT");
1843 }
1844 
Dump(unsigned short ntabs) const1845 std::string Armed::Dump(unsigned short ntabs) const
1846 { return DumpIndent(ntabs) + "Armed\n"; }
1847 
Match(const ScriptingContext & local_context) const1848 bool Armed::Match(const ScriptingContext& local_context) const {
1849     auto candidate = local_context.condition_local_candidate;
1850     if (!candidate) {
1851         ErrorLogger() << "Armed::Match passed no candidate object";
1852         return false;
1853     }
1854 
1855     if (auto ship = std::dynamic_pointer_cast<const Ship>(candidate))
1856         if (ship->IsArmed())
1857             return true;
1858 
1859     return false;
1860 }
1861 
GetCheckSum() const1862 unsigned int Armed::GetCheckSum() const {
1863     unsigned int retval{0};
1864 
1865     CheckSums::CheckSumCombine(retval, "Condition::Armed");
1866 
1867     TraceLogger() << "GetCheckSum(Armed): retval: " << retval;
1868     return retval;
1869 }
1870 
1871 ///////////////////////////////////////////////////////////
1872 // Type                                                  //
1873 ///////////////////////////////////////////////////////////
Type(std::unique_ptr<ValueRef::ValueRef<UniverseObjectType>> && type)1874 Type::Type(std::unique_ptr<ValueRef::ValueRef<UniverseObjectType>>&& type) :
1875     Condition(),
1876     m_type(std::move(type))
1877 {
1878     m_root_candidate_invariant = m_type->RootCandidateInvariant();
1879     m_target_invariant = m_type->TargetInvariant();
1880     m_source_invariant = m_type->SourceInvariant();
1881 }
1882 
Type(UniverseObjectType type)1883 Type::Type(UniverseObjectType type) :
1884     Type(std::make_unique<ValueRef::Constant<UniverseObjectType>>(type))
1885 {}
1886 
operator ==(const Condition & rhs) const1887 bool Type::operator==(const Condition& rhs) const {
1888     if (this == &rhs)
1889         return true;
1890     if (typeid(*this) != typeid(rhs))
1891         return false;
1892 
1893     const Type& rhs_ = static_cast<const Type&>(rhs);
1894 
1895     CHECK_COND_VREF_MEMBER(m_type)
1896 
1897     return true;
1898 }
1899 
1900 namespace {
1901     struct TypeSimpleMatch {
TypeSimpleMatchCondition::__anone9a56faf0e11::TypeSimpleMatch1902         TypeSimpleMatch(UniverseObjectType type) :
1903             m_type(type)
1904         {}
1905 
operator ()Condition::__anone9a56faf0e11::TypeSimpleMatch1906         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
1907             if (!candidate)
1908                 return false;
1909 
1910             switch (m_type) {
1911             case OBJ_BUILDING:
1912             case OBJ_SHIP:
1913             case OBJ_FLEET:
1914             case OBJ_PLANET:
1915             case OBJ_SYSTEM:
1916             case OBJ_FIELD:
1917             case OBJ_FIGHTER:
1918                 return candidate->ObjectType() == m_type;
1919                 break;
1920             case OBJ_POP_CENTER:
1921                 return (bool)std::dynamic_pointer_cast<const PopCenter>(candidate);
1922                 break;
1923             case OBJ_PROD_CENTER:
1924                 return (bool)std::dynamic_pointer_cast<const ResourceCenter>(candidate);
1925                 break;
1926             default:
1927                 break;
1928             }
1929             return false;
1930         }
1931 
1932         UniverseObjectType m_type;
1933     };
1934 }
1935 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const1936 void Type::Eval(const ScriptingContext& parent_context,
1937                 ObjectSet& matches, ObjectSet& non_matches,
1938                 SearchDomain search_domain/* = NON_MATCHES*/) const
1939 {
1940     bool simple_eval_safe = m_type->ConstantExpr() ||
1941                             (m_type->LocalCandidateInvariant() &&
1942                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
1943     if (simple_eval_safe) {
1944         UniverseObjectType type = m_type->Eval(parent_context);
1945         EvalImpl(matches, non_matches, search_domain, TypeSimpleMatch(type));
1946     } else {
1947         // re-evaluate allowed turn range for each candidate object
1948         Condition::Eval(parent_context, matches, non_matches, search_domain);
1949     }
1950 }
1951 
Description(bool negated) const1952 std::string Type::Description(bool negated/* = false*/) const {
1953     std::string value_str = m_type->ConstantExpr() ?
1954                                 UserString(boost::lexical_cast<std::string>(m_type->Eval())) :
1955                                 m_type->Description();
1956     return str(FlexibleFormat((!negated)
1957            ? UserString("DESC_TYPE")
1958            : UserString("DESC_TYPE_NOT"))
1959            % value_str);
1960 }
1961 
Dump(unsigned short ntabs) const1962 std::string Type::Dump(unsigned short ntabs) const {
1963     std::string retval = DumpIndent(ntabs);
1964     if (dynamic_cast<ValueRef::Constant<UniverseObjectType>*>(m_type.get())) {
1965         switch (m_type->Eval()) {
1966         case OBJ_BUILDING:    retval += "Building\n"; break;
1967         case OBJ_SHIP:        retval += "Ship\n"; break;
1968         case OBJ_FLEET:       retval += "Fleet\n"; break;
1969         case OBJ_PLANET:      retval += "Planet\n"; break;
1970         case OBJ_POP_CENTER:  retval += "PopulationCenter\n"; break;
1971         case OBJ_PROD_CENTER: retval += "ProductionCenter\n"; break;
1972         case OBJ_SYSTEM:      retval += "System\n"; break;
1973         case OBJ_FIELD:       retval += "Field\n"; break;
1974         case OBJ_FIGHTER:     retval += "Fighter\n"; break;
1975         default: retval += "?\n"; break;
1976         }
1977     } else {
1978         retval += "ObjectType type = " + m_type->Dump(ntabs) + "\n";
1979     }
1980     return retval;
1981 }
1982 
Match(const ScriptingContext & local_context) const1983 bool Type::Match(const ScriptingContext& local_context) const {
1984     auto candidate = local_context.condition_local_candidate;
1985     if (!candidate) {
1986         ErrorLogger() << "Type::Match passed no candidate object";
1987         return false;
1988     }
1989 
1990     return TypeSimpleMatch(m_type->Eval(local_context))(candidate);
1991 }
1992 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const1993 void Type::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
1994                                              ObjectSet& condition_non_targets) const
1995 {
1996     // Ships, Fleets and default checks for current objects only
1997     bool found_type = false;
1998     if (m_type) {
1999         found_type = true;
2000         //std::vector<std::shared_ptr<const T>> this_base;
2001         switch (m_type->Eval()) {
2002             case OBJ_BUILDING:
2003                 AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
2004                 break;
2005             case OBJ_FIELD:
2006                 AddFieldSet(parent_context.ContextObjects(), condition_non_targets);
2007                 break;
2008             case OBJ_FLEET:
2009                 AddFleetSet(parent_context.ContextObjects(), condition_non_targets);
2010                 break;
2011             case OBJ_PLANET:
2012                 AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
2013                 break;
2014             case OBJ_POP_CENTER:
2015                 AddPopCenterSet(parent_context.ContextObjects(), condition_non_targets);
2016                 break;
2017             case OBJ_PROD_CENTER:
2018                 AddResCenterSet(parent_context.ContextObjects(), condition_non_targets);
2019                 break;
2020             case OBJ_SHIP:
2021                 AddShipSet(parent_context.ContextObjects(), condition_non_targets);
2022                 break;
2023             case OBJ_SYSTEM:
2024                 AddSystemSet(parent_context.ContextObjects(), condition_non_targets);
2025                 break;
2026             case OBJ_FIGHTER:   // shouldn't exist outside of combat as a separate object
2027             default:
2028                 found_type = false;
2029                 break;
2030         }
2031     }
2032     if (found_type) {
2033         //if (int(condition_non_targets.size()) < parent_context.ContextObjects().size()) {
2034         //    DebugLogger() << "Type::GetBaseNonMatches will provide " << condition_non_targets.size()
2035         //                  << " objects of type " << GetType()->Eval() << " rather than "
2036         //                  << parent_context.ContextObjects().size() << " total objects";
2037         //}
2038     } else {
2039         Condition::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
2040     }
2041 }
2042 
SetTopLevelContent(const std::string & content_name)2043 void Type::SetTopLevelContent(const std::string& content_name) {
2044     if (m_type)
2045         m_type->SetTopLevelContent(content_name);
2046 }
2047 
GetCheckSum() const2048 unsigned int Type::GetCheckSum() const {
2049     unsigned int retval{0};
2050 
2051     CheckSums::CheckSumCombine(retval, "Condition::Type");
2052     CheckSums::CheckSumCombine(retval, m_type);
2053 
2054     TraceLogger() << "GetCheckSum(Type): retval: " << retval;
2055     return retval;
2056 }
2057 
2058 ///////////////////////////////////////////////////////////
2059 // Building                                              //
2060 ///////////////////////////////////////////////////////////
Building(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>> && names)2061 Building::Building(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>>&& names) :
2062     Condition(),
2063     m_names(std::move(names))
2064 {
2065     m_root_candidate_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->RootCandidateInvariant(); });
2066     m_target_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->TargetInvariant(); });
2067     m_source_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->SourceInvariant(); });
2068 }
2069 
operator ==(const Condition & rhs) const2070 bool Building::operator==(const Condition& rhs) const {
2071     if (this == &rhs)
2072         return true;
2073     if (typeid(*this) != typeid(rhs))
2074         return false;
2075 
2076     const Building& rhs_ = static_cast<const Building&>(rhs);
2077 
2078     if (m_names.size() != rhs_.m_names.size())
2079         return false;
2080     for (unsigned int i = 0; i < m_names.size(); ++i) {
2081         CHECK_COND_VREF_MEMBER(m_names.at(i))
2082     }
2083 
2084     return true;
2085 }
2086 
2087 namespace {
2088     struct BuildingSimpleMatch {
BuildingSimpleMatchCondition::__anone9a56faf1211::BuildingSimpleMatch2089         BuildingSimpleMatch(const std::vector<std::string>& names) :
2090             m_names(names)
2091         {}
2092 
operator ()Condition::__anone9a56faf1211::BuildingSimpleMatch2093         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
2094             if (!candidate)
2095                 return false;
2096 
2097             // is it a building?
2098             auto building = std::dynamic_pointer_cast<const ::Building>(candidate);
2099             if (!building)
2100                 return false;
2101 
2102             // if no name supplied, match any building
2103             if (m_names.empty())
2104                 return true;
2105 
2106             // is it one of the specified building types?
2107             return std::count(m_names.begin(), m_names.end(), building->BuildingTypeName());
2108         }
2109 
2110         const std::vector<std::string>& m_names;
2111     };
2112 }
2113 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const2114 void Building::Eval(const ScriptingContext& parent_context,
2115                     ObjectSet& matches, ObjectSet& non_matches,
2116                     SearchDomain search_domain/* = NON_MATCHES*/) const
2117 {
2118     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
2119     if (simple_eval_safe) {
2120         // check each valueref for invariance to local candidate
2121         for (auto& name : m_names) {
2122             if (!name->LocalCandidateInvariant()) {
2123                 simple_eval_safe = false;
2124                 break;
2125             }
2126         }
2127     }
2128     if (simple_eval_safe) {
2129         // evaluate names once, and use to check all candidate objects
2130         std::vector<std::string> names;
2131         // get all names from valuerefs
2132         for (auto& name : m_names) {
2133             names.push_back(name->Eval(parent_context));
2134         }
2135         EvalImpl(matches, non_matches, search_domain, BuildingSimpleMatch(names));
2136     } else {
2137         // re-evaluate allowed building types range for each candidate object
2138         Condition::Eval(parent_context, matches, non_matches, search_domain);
2139     }
2140 }
2141 
Description(bool negated) const2142 std::string Building::Description(bool negated/* = false*/) const {
2143     std::string values_str;
2144     for (unsigned int i = 0; i < m_names.size(); ++i) {
2145         values_str += m_names[i]->ConstantExpr() ?
2146                         UserString(m_names[i]->Eval()) :
2147                         m_names[i]->Description();
2148         if (2 <= m_names.size() && i < m_names.size() - 2) {
2149             values_str += ", ";
2150         } else if (i == m_names.size() - 2) {
2151             values_str += m_names.size() < 3 ? " " : ", ";
2152             values_str += UserString("OR");
2153             values_str += " ";
2154         }
2155     }
2156     return str(FlexibleFormat((!negated)
2157            ? UserString("DESC_BUILDING")
2158            : UserString("DESC_BUILDING_NOT"))
2159            % values_str);
2160 }
2161 
Dump(unsigned short ntabs) const2162 std::string Building::Dump(unsigned short ntabs) const {
2163     std::string retval = DumpIndent(ntabs) + "Building name = ";
2164     if (m_names.size() == 1) {
2165         retval += m_names[0]->Dump(ntabs) + "\n";
2166     } else {
2167         retval += "[ ";
2168         for (auto& name : m_names) {
2169             retval += name->Dump(ntabs) + " ";
2170         }
2171         retval += "]\n";
2172     }
2173     return retval;
2174 }
2175 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const2176 void Building::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
2177                                                  ObjectSet& condition_non_targets) const
2178 { AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); }
2179 
Match(const ScriptingContext & local_context) const2180 bool Building::Match(const ScriptingContext& local_context) const {
2181     auto candidate = local_context.condition_local_candidate;
2182     if (!candidate) {
2183         ErrorLogger() << "Building::Match passed no candidate object";
2184         return false;
2185     }
2186 
2187     // is it a building?
2188     auto building = std::dynamic_pointer_cast<const ::Building>(candidate);
2189     if (building) {
2190         // match any building type?
2191         if (m_names.empty())
2192             return true;
2193 
2194         // match one of the specified building types
2195         for (auto& name : m_names) {
2196             if (name->Eval(local_context) == building->BuildingTypeName())
2197                 return true;
2198         }
2199     }
2200 
2201     return false;
2202 }
2203 
SetTopLevelContent(const std::string & content_name)2204 void Building::SetTopLevelContent(const std::string& content_name) {
2205     for (auto& name : m_names) {
2206         if (name)
2207             name->SetTopLevelContent(content_name);
2208     }
2209 }
2210 
GetCheckSum() const2211 unsigned int Building::GetCheckSum() const {
2212     unsigned int retval{0};
2213 
2214     CheckSums::CheckSumCombine(retval, "Condition::Building");
2215     CheckSums::CheckSumCombine(retval, m_names);
2216 
2217     TraceLogger() << "GetCheckSum(Building): retval: " << retval;
2218     return retval;
2219 }
2220 
2221 ///////////////////////////////////////////////////////////
2222 // HasSpecial                                            //
2223 ///////////////////////////////////////////////////////////
HasSpecial()2224 HasSpecial::HasSpecial() :
2225     HasSpecial(nullptr,
2226                std::unique_ptr<ValueRef::ValueRef<int>>{},
2227                std::unique_ptr<ValueRef::ValueRef<int>>{})
2228 {}
2229 
HasSpecial(std::string name)2230 HasSpecial::HasSpecial(std::string name) :
2231     HasSpecial(std::make_unique<ValueRef::Constant<std::string>>(std::move(name)),
2232                std::unique_ptr<ValueRef::ValueRef<int>>{},
2233                std::unique_ptr<ValueRef::ValueRef<int>>{})
2234 {}
2235 
HasSpecial(std::unique_ptr<ValueRef::ValueRef<std::string>> && name)2236 HasSpecial::HasSpecial(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
2237     HasSpecial(std::move(name),
2238                std::unique_ptr<ValueRef::ValueRef<int>>{},
2239                std::unique_ptr<ValueRef::ValueRef<int>>{})
2240 {}
2241 
HasSpecial(std::unique_ptr<ValueRef::ValueRef<std::string>> && name,std::unique_ptr<ValueRef::ValueRef<int>> && since_turn_low,std::unique_ptr<ValueRef::ValueRef<int>> && since_turn_high)2242 HasSpecial::HasSpecial(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name,
2243                        std::unique_ptr<ValueRef::ValueRef<int>>&& since_turn_low,
2244                        std::unique_ptr<ValueRef::ValueRef<int>>&& since_turn_high) :
2245     Condition(),
2246     m_name(std::move(name)),
2247     m_since_turn_low(std::move(since_turn_low)),
2248     m_since_turn_high(std::move(since_turn_high))
2249 {
2250     auto operands = {m_since_turn_low.get(), m_since_turn_high.get()};
2251     m_root_candidate_invariant =
2252         (!m_name || m_name->RootCandidateInvariant()) &&
2253         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
2254     m_target_invariant =
2255         (!m_name || m_name->TargetInvariant()) &&
2256         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
2257     m_source_invariant =
2258         (!m_name || m_name->SourceInvariant()) &&
2259         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
2260 }
2261 
HasSpecial(std::unique_ptr<ValueRef::ValueRef<std::string>> && name,std::unique_ptr<ValueRef::ValueRef<double>> && capacity_low,std::unique_ptr<ValueRef::ValueRef<double>> && capacity_high)2262 HasSpecial::HasSpecial(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name,
2263                        std::unique_ptr<ValueRef::ValueRef<double>>&& capacity_low,
2264                        std::unique_ptr<ValueRef::ValueRef<double>>&& capacity_high) :
2265     Condition(),
2266     m_name(std::move(name)),
2267     m_capacity_low(std::move(capacity_low)),
2268     m_capacity_high(std::move(capacity_high))
2269 {
2270     auto operands = {m_capacity_low.get(), m_capacity_high.get()};
2271     m_root_candidate_invariant =
2272         (!m_name || m_name->RootCandidateInvariant()) &&
2273         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
2274     m_target_invariant =
2275         (!m_name || m_name->TargetInvariant()) &&
2276         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
2277     m_source_invariant =
2278         (!m_name || m_name->SourceInvariant()) &&
2279         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
2280 }
2281 
operator ==(const Condition & rhs) const2282 bool HasSpecial::operator==(const Condition& rhs) const {
2283     if (this == &rhs)
2284         return true;
2285     if (typeid(*this) != typeid(rhs))
2286         return false;
2287 
2288     const HasSpecial& rhs_ = static_cast<const HasSpecial&>(rhs);
2289 
2290     CHECK_COND_VREF_MEMBER(m_name)
2291     CHECK_COND_VREF_MEMBER(m_capacity_low)
2292     CHECK_COND_VREF_MEMBER(m_capacity_high)
2293     CHECK_COND_VREF_MEMBER(m_since_turn_low)
2294     CHECK_COND_VREF_MEMBER(m_since_turn_high)
2295 
2296     return true;
2297 }
2298 
2299 namespace {
2300     struct HasSpecialSimpleMatch {
HasSpecialSimpleMatchCondition::__anone9a56faf1911::HasSpecialSimpleMatch2301         HasSpecialSimpleMatch(const std::string& name, float low_cap, float high_cap, int low_turn, int high_turn) :
2302             m_name(name),
2303             m_low_cap(low_cap),
2304             m_high_cap(high_cap),
2305             m_low_turn(low_turn),
2306             m_high_turn(high_turn)
2307         {}
2308 
operator ()Condition::__anone9a56faf1911::HasSpecialSimpleMatch2309         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
2310             if (!candidate)
2311                 return false;
2312 
2313             if (m_name.empty())
2314                 return !candidate->Specials().empty();
2315 
2316             auto it = candidate->Specials().find(m_name);
2317             if (it == candidate->Specials().end())
2318                 return false;
2319 
2320             int special_since_turn = it->second.first;
2321             float special_capacity = it->second.second;
2322             return m_low_turn <= special_since_turn
2323                 && special_since_turn <= m_high_turn
2324                 && m_low_cap <= special_capacity
2325                 && special_capacity <= m_high_cap;
2326         }
2327 
2328         const std::string&  m_name;
2329         float               m_low_cap;
2330         float               m_high_cap;
2331         int                 m_low_turn;
2332         int                 m_high_turn;
2333     };
2334 }
2335 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const2336 void HasSpecial::Eval(const ScriptingContext& parent_context,
2337                       ObjectSet& matches, ObjectSet& non_matches,
2338                       SearchDomain search_domain/* = NON_MATCHES*/) const
2339 {
2340     bool simple_eval_safe = ((!m_name || m_name->LocalCandidateInvariant()) &&
2341                              (!m_capacity_low || m_capacity_low->LocalCandidateInvariant()) &&
2342                              (!m_capacity_high || m_capacity_high->LocalCandidateInvariant()) &&
2343                              (!m_since_turn_low || m_since_turn_low->LocalCandidateInvariant()) &&
2344                              (!m_since_turn_high || m_since_turn_high->LocalCandidateInvariant()) &&
2345                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
2346     if (simple_eval_safe) {
2347         // evaluate turn limits and capacities once, pass to simple match for all candidates
2348         std::string name = (m_name ? m_name->Eval(parent_context) : "");
2349         float low_cap = (m_capacity_low ? m_capacity_low->Eval(parent_context) : -FLT_MAX);
2350         float high_cap = (m_capacity_high ? m_capacity_high->Eval(parent_context) : FLT_MAX);
2351         int low_turn = (m_since_turn_low ? m_since_turn_low->Eval(parent_context) : BEFORE_FIRST_TURN);
2352         int high_turn = (m_since_turn_high ? m_since_turn_high->Eval(parent_context) : IMPOSSIBLY_LARGE_TURN);
2353         EvalImpl(matches, non_matches, search_domain, HasSpecialSimpleMatch(name, low_cap, high_cap, low_turn, high_turn));
2354     } else {
2355         // re-evaluate allowed turn range for each candidate object
2356         Condition::Eval(parent_context, matches, non_matches, search_domain);
2357     }
2358 }
2359 
Description(bool negated) const2360 std::string HasSpecial::Description(bool negated/* = false*/) const {
2361     std::string name_str;
2362     if (m_name) {
2363         name_str = m_name->Description();
2364         if (m_name->ConstantExpr() && UserStringExists(name_str))
2365             name_str = UserString(name_str);
2366     }
2367 
2368     if (m_since_turn_low || m_since_turn_high) {
2369         // turn range has been specified; must indicate in description
2370         std::string low_str = std::to_string(BEFORE_FIRST_TURN);
2371         if (m_since_turn_low)
2372             low_str = m_since_turn_low->Description();
2373 
2374         std::string high_str = std::to_string(IMPOSSIBLY_LARGE_TURN);
2375         if (m_since_turn_high)
2376             high_str = m_since_turn_high->Description();
2377 
2378         return str(FlexibleFormat((!negated)
2379             ? UserString("DESC_SPECIAL_TURN_RANGE")
2380             : UserString("DESC_SPECIAL_TURN_RANGE_NOT"))
2381                 % name_str
2382                 % low_str
2383                 % high_str);
2384     }
2385 
2386     if (m_capacity_low || m_capacity_high) {
2387         // capacity range has been specified; must indicate in description
2388         std::string low_str = std::to_string(-FLT_MAX);
2389         if (m_capacity_low)
2390             low_str = m_capacity_low->Description();
2391 
2392         std::string high_str = std::to_string(FLT_MAX);
2393         if (m_capacity_high)
2394             high_str = m_capacity_high->Description();
2395 
2396         return str(FlexibleFormat((!negated)
2397             ? UserString("DESC_SPECIAL_CAPACITY_RANGE")
2398             : UserString("DESC_SPECIAL_CAPACITY_RANGE_NOT"))
2399                 % name_str
2400                 % low_str
2401                 % high_str);
2402     }
2403 
2404     return str(FlexibleFormat((!negated)
2405         ? UserString("DESC_SPECIAL")
2406         : UserString("DESC_SPECIAL_NOT"))
2407                 % name_str);
2408 }
2409 
Dump(unsigned short ntabs) const2410 std::string HasSpecial::Dump(unsigned short ntabs) const {
2411     std::string name_str = (m_name ? m_name->Dump(ntabs) : "");
2412 
2413     if (m_since_turn_low || m_since_turn_high) {
2414         std::string low_dump = (m_since_turn_low ? m_since_turn_low->Dump(ntabs) : std::to_string(BEFORE_FIRST_TURN));
2415         std::string high_dump = (m_since_turn_high ? m_since_turn_high->Dump(ntabs) : std::to_string(IMPOSSIBLY_LARGE_TURN));
2416         return DumpIndent(ntabs) + "HasSpecialSinceTurn name = \"" + name_str + "\" low = " + low_dump + " high = " + high_dump;
2417     }
2418 
2419     if (m_capacity_low || m_capacity_high) {
2420         std::string low_dump = (m_capacity_low ? m_capacity_low->Dump(ntabs) : std::to_string(-FLT_MAX));
2421         std::string high_dump = (m_capacity_high ? m_capacity_high->Dump(ntabs) : std::to_string(FLT_MAX));
2422         return DumpIndent(ntabs) + "HasSpecialCapacity name = \"" + name_str + "\" low = " + low_dump + " high = " + high_dump;
2423     }
2424 
2425     return DumpIndent(ntabs) + "HasSpecial name = \"" + name_str + "\"\n";
2426 }
2427 
Match(const ScriptingContext & local_context) const2428 bool HasSpecial::Match(const ScriptingContext& local_context) const {
2429     auto candidate = local_context.condition_local_candidate;
2430     if (!candidate) {
2431         ErrorLogger() << "HasSpecial::Match passed no candidate object";
2432         return false;
2433     }
2434     std::string name = (m_name ? m_name->Eval(local_context) : "");
2435     float low_cap = (m_capacity_low ? m_capacity_low->Eval(local_context) : -FLT_MAX);
2436     float high_cap = (m_capacity_high ? m_capacity_high->Eval(local_context) : FLT_MAX);
2437     int low_turn = (m_since_turn_low ? m_since_turn_low->Eval(local_context) : BEFORE_FIRST_TURN);
2438     int high_turn = (m_since_turn_high ? m_since_turn_high->Eval(local_context) : IMPOSSIBLY_LARGE_TURN);
2439 
2440     return HasSpecialSimpleMatch(name, low_cap, high_cap, low_turn, high_turn)(candidate);
2441 }
2442 
SetTopLevelContent(const std::string & content_name)2443 void HasSpecial::SetTopLevelContent(const std::string& content_name) {
2444     if (m_name)
2445         m_name->SetTopLevelContent(content_name);
2446     if (m_capacity_low)
2447         m_capacity_low->SetTopLevelContent(content_name);
2448     if (m_capacity_high)
2449         m_capacity_high->SetTopLevelContent(content_name);
2450     if (m_since_turn_low)
2451         m_since_turn_low->SetTopLevelContent(content_name);
2452     if (m_since_turn_high)
2453         m_since_turn_high->SetTopLevelContent(content_name);
2454 }
2455 
GetCheckSum() const2456 unsigned int HasSpecial::GetCheckSum() const {
2457     unsigned int retval{0};
2458 
2459     CheckSums::CheckSumCombine(retval, "Condition::HasSpecial");
2460     CheckSums::CheckSumCombine(retval, m_name);
2461     CheckSums::CheckSumCombine(retval, m_capacity_low);
2462     CheckSums::CheckSumCombine(retval, m_capacity_high);
2463     CheckSums::CheckSumCombine(retval, m_since_turn_low);
2464     CheckSums::CheckSumCombine(retval, m_since_turn_high);
2465 
2466     TraceLogger() << "GetCheckSum(HasSpecial): retval: " << retval;
2467     return retval;
2468 }
2469 
2470 ///////////////////////////////////////////////////////////
2471 // HasTag                                                //
2472 ///////////////////////////////////////////////////////////
HasTag()2473 HasTag::HasTag() :
2474     HasTag(std::unique_ptr<ValueRef::ValueRef<std::string>>{})
2475 {}
2476 
HasTag(const std::string & name)2477 HasTag::HasTag(const std::string& name) :
2478     HasTag(std::make_unique<ValueRef::Constant<std::string>>(name))
2479 {}
2480 
HasTag(std::unique_ptr<ValueRef::ValueRef<std::string>> && name)2481 HasTag::HasTag(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
2482     Condition(),
2483     m_name(std::move(name))
2484 {
2485     m_root_candidate_invariant = !m_name || m_name->RootCandidateInvariant();
2486     m_target_invariant = !m_name || m_name->TargetInvariant();
2487     m_source_invariant = !m_name || m_name->SourceInvariant();
2488 }
2489 
operator ==(const Condition & rhs) const2490 bool HasTag::operator==(const Condition& rhs) const {
2491     if (this == &rhs)
2492         return true;
2493     if (typeid(*this) != typeid(rhs))
2494         return false;
2495 
2496     const HasTag& rhs_ = static_cast<const HasTag&>(rhs);
2497 
2498     CHECK_COND_VREF_MEMBER(m_name)
2499 
2500     return true;
2501 }
2502 
2503 namespace {
2504     struct HasTagSimpleMatch {
HasTagSimpleMatchCondition::__anone9a56faf1a11::HasTagSimpleMatch2505         HasTagSimpleMatch() :
2506             m_any_tag_ok(true),
2507             m_name()
2508         {}
2509 
HasTagSimpleMatchCondition::__anone9a56faf1a11::HasTagSimpleMatch2510         HasTagSimpleMatch(const std::string& name) :
2511             m_any_tag_ok(false),
2512             m_name(name)
2513         {}
2514 
operator ()Condition::__anone9a56faf1a11::HasTagSimpleMatch2515         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
2516             if (!candidate)
2517                 return false;
2518 
2519             if (m_any_tag_ok && !candidate->Tags().empty())
2520                 return true;
2521 
2522             return candidate->HasTag(m_name);
2523         }
2524 
2525         bool        m_any_tag_ok;
2526         std::string m_name;
2527     };
2528 }
2529 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const2530 void HasTag::Eval(const ScriptingContext& parent_context,
2531                   ObjectSet& matches, ObjectSet& non_matches,
2532                   SearchDomain search_domain/* = NON_MATCHES*/) const
2533 {
2534     bool simple_eval_safe = (!m_name || m_name->LocalCandidateInvariant()) &&
2535                             (parent_context.condition_root_candidate || RootCandidateInvariant());
2536     if (simple_eval_safe) {
2537         // evaluate number limits once, use to match all candidates
2538         if (!m_name) {
2539             EvalImpl(matches, non_matches, search_domain, HasTagSimpleMatch());
2540         } else {
2541             std::string name = boost::to_upper_copy<std::string>(m_name->Eval(parent_context));
2542             EvalImpl(matches, non_matches, search_domain, HasTagSimpleMatch(name));
2543         }
2544     } else {
2545         // re-evaluate allowed turn range for each candidate object
2546         Condition::Eval(parent_context, matches, non_matches, search_domain);
2547     }
2548 }
2549 
Description(bool negated) const2550 std::string HasTag::Description(bool negated/* = false*/) const {
2551     std::string name_str;
2552     if (m_name) {
2553         name_str = m_name->Description();
2554         if (m_name->ConstantExpr() && UserStringExists(name_str))
2555             name_str = UserString(name_str);
2556     }
2557     return str(FlexibleFormat((!negated)
2558         ? UserString("DESC_HAS_TAG")
2559         : UserString("DESC_HAS_TAG_NOT"))
2560         % name_str);
2561 }
2562 
Dump(unsigned short ntabs) const2563 std::string HasTag::Dump(unsigned short ntabs) const {
2564     std::string retval = DumpIndent(ntabs) + "HasTag";
2565     if (m_name)
2566         retval += " name = " + m_name->Dump(ntabs);
2567     retval += "\n";
2568     return retval;
2569 }
2570 
Match(const ScriptingContext & local_context) const2571 bool HasTag::Match(const ScriptingContext& local_context) const {
2572     auto candidate = local_context.condition_local_candidate;
2573     if (!candidate) {
2574         ErrorLogger() << "HasTag::Match passed no candidate object";
2575         return false;
2576     }
2577 
2578     if (!m_name)
2579         return HasTagSimpleMatch()(candidate);
2580 
2581     std::string name = boost::to_upper_copy<std::string>(m_name->Eval(local_context));
2582     return HasTagSimpleMatch(name)(candidate);
2583 }
2584 
SetTopLevelContent(const std::string & content_name)2585 void HasTag::SetTopLevelContent(const std::string& content_name) {
2586     if (m_name)
2587         m_name->SetTopLevelContent(content_name);
2588 }
2589 
GetCheckSum() const2590 unsigned int HasTag::GetCheckSum() const {
2591     unsigned int retval{0};
2592 
2593     CheckSums::CheckSumCombine(retval, "Condition::HasTag");
2594     CheckSums::CheckSumCombine(retval, m_name);
2595 
2596     TraceLogger() << "GetCheckSum(HasTag): retval: " << retval;
2597     return retval;
2598 }
2599 
2600 ///////////////////////////////////////////////////////////
2601 // CreatedOnTurn                                         //
2602 ///////////////////////////////////////////////////////////
CreatedOnTurn(std::unique_ptr<ValueRef::ValueRef<int>> && low,std::unique_ptr<ValueRef::ValueRef<int>> && high)2603 CreatedOnTurn::CreatedOnTurn(std::unique_ptr<ValueRef::ValueRef<int>>&& low,
2604                              std::unique_ptr<ValueRef::ValueRef<int>>&& high) :
2605     Condition(),
2606     m_low(std::move(low)),
2607     m_high(std::move(high))
2608 {
2609     auto operands = {m_low.get(), m_high.get()};
2610     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
2611     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
2612     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
2613 }
2614 
operator ==(const Condition & rhs) const2615 bool CreatedOnTurn::operator==(const Condition& rhs) const {
2616     if (this == &rhs)
2617         return true;
2618     if (typeid(*this) != typeid(rhs))
2619         return false;
2620 
2621     const CreatedOnTurn& rhs_ = static_cast<const CreatedOnTurn&>(rhs);
2622 
2623     CHECK_COND_VREF_MEMBER(m_low)
2624     CHECK_COND_VREF_MEMBER(m_high)
2625 
2626     return true;
2627 }
2628 
2629 namespace {
2630     struct CreatedOnTurnSimpleMatch {
CreatedOnTurnSimpleMatchCondition::__anone9a56faf1e11::CreatedOnTurnSimpleMatch2631         CreatedOnTurnSimpleMatch(int low, int high) :
2632             m_low(low),
2633             m_high(high)
2634         {}
2635 
operator ()Condition::__anone9a56faf1e11::CreatedOnTurnSimpleMatch2636         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
2637             if (!candidate)
2638                 return false;
2639             int turn = candidate->CreationTurn();
2640             return m_low <= turn && turn <= m_high;
2641         }
2642 
2643         int m_low;
2644         int m_high;
2645     };
2646 }
2647 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const2648 void CreatedOnTurn::Eval(const ScriptingContext& parent_context,
2649                          ObjectSet& matches, ObjectSet& non_matches,
2650                          SearchDomain search_domain/* = NON_MATCHES*/) const
2651 {
2652     bool simple_eval_safe = ((!m_low || m_low->LocalCandidateInvariant()) &&
2653                              (!m_high || m_high->LocalCandidateInvariant()) &&
2654                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
2655     if (simple_eval_safe) {
2656         int low = (m_low ? m_low->Eval(parent_context) : BEFORE_FIRST_TURN);
2657         int high = (m_high ? m_high->Eval(parent_context) : IMPOSSIBLY_LARGE_TURN);
2658         EvalImpl(matches, non_matches, search_domain, CreatedOnTurnSimpleMatch(low, high));
2659     } else {
2660         // re-evaluate allowed turn range for each candidate object
2661         Condition::Eval(parent_context, matches, non_matches, search_domain);
2662     }
2663 }
2664 
Description(bool negated) const2665 std::string CreatedOnTurn::Description(bool negated/* = false*/) const {
2666     std::string low_str = (m_low ? (m_low->ConstantExpr() ?
2667                                     std::to_string(m_low->Eval()) :
2668                                     m_low->Description())
2669                                  : std::to_string(BEFORE_FIRST_TURN));
2670     std::string high_str = (m_high ? (m_high->ConstantExpr() ?
2671                                       std::to_string(m_high->Eval()) :
2672                                       m_high->Description())
2673                                    : std::to_string(IMPOSSIBLY_LARGE_TURN));
2674     return str(FlexibleFormat((!negated)
2675             ? UserString("DESC_CREATED_ON_TURN")
2676             : UserString("DESC_CREATED_ON_TURN_NOT"))
2677                % low_str
2678                % high_str);
2679 }
2680 
Dump(unsigned short ntabs) const2681 std::string CreatedOnTurn::Dump(unsigned short ntabs) const {
2682     std::string retval = DumpIndent(ntabs) + "CreatedOnTurn";
2683     if (m_low)
2684         retval += " low = " + m_low->Dump(ntabs);
2685     if (m_high)
2686         retval += " high = " + m_high->Dump(ntabs);
2687     retval += "\n";
2688     return retval;
2689 }
2690 
Match(const ScriptingContext & local_context) const2691 bool CreatedOnTurn::Match(const ScriptingContext& local_context) const {
2692     auto candidate = local_context.condition_local_candidate;
2693     if (!candidate) {
2694         ErrorLogger() << "CreatedOnTurn::Match passed no candidate object";
2695         return false;
2696     }
2697     int low = (m_low ? std::max(0, m_low->Eval(local_context)) : BEFORE_FIRST_TURN);
2698     int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN);
2699     return CreatedOnTurnSimpleMatch(low, high)(candidate);
2700 }
2701 
SetTopLevelContent(const std::string & content_name)2702 void CreatedOnTurn::SetTopLevelContent(const std::string& content_name) {
2703     if (m_low)
2704         m_low->SetTopLevelContent(content_name);
2705     if (m_high)
2706         m_high->SetTopLevelContent(content_name);
2707 }
2708 
GetCheckSum() const2709 unsigned int CreatedOnTurn::GetCheckSum() const {
2710     unsigned int retval{0};
2711 
2712     CheckSums::CheckSumCombine(retval, "Condition::CreatedOnTurn");
2713     CheckSums::CheckSumCombine(retval, m_low);
2714     CheckSums::CheckSumCombine(retval, m_high);
2715 
2716     TraceLogger() << "GetCheckSum(CreatedOnTurn): retval: " << retval;
2717     return retval;
2718 }
2719 
2720 ///////////////////////////////////////////////////////////
2721 // Contains                                              //
2722 ///////////////////////////////////////////////////////////
Contains(std::unique_ptr<Condition> && condition)2723 Contains::Contains(std::unique_ptr<Condition>&& condition) :
2724     Condition(),
2725     m_condition(std::move(condition))
2726 {
2727     m_root_candidate_invariant = m_condition->RootCandidateInvariant();
2728     m_target_invariant = m_condition->TargetInvariant();
2729     m_source_invariant = m_condition->SourceInvariant();
2730 }
2731 
operator ==(const Condition & rhs) const2732 bool Contains::operator==(const Condition& rhs) const {
2733     if (this == &rhs)
2734         return true;
2735     if (typeid(*this) != typeid(rhs))
2736         return false;
2737 
2738     const Contains& rhs_ = static_cast<const Contains&>(rhs);
2739 
2740     CHECK_COND_VREF_MEMBER(m_condition)
2741 
2742     return true;
2743 }
2744 
2745 namespace {
2746     struct ContainsSimpleMatch {
ContainsSimpleMatchCondition::__anone9a56faf1f11::ContainsSimpleMatch2747         ContainsSimpleMatch(const ObjectSet& subcondition_matches) :
2748             m_subcondition_matches_ids()
2749         {
2750             // We need a sorted container for efficiently intersecting
2751             // subcondition_matches with the set of objects contained in some
2752             // candidate object.
2753             // We only need ids, not objects, so we can do that conversion
2754             // here as well, simplifying later code.
2755             // Note that this constructor is called only once per
2756             // Contains::Eval(), its work cannot help performance when executed
2757             // for each candidate.
2758             m_subcondition_matches_ids.reserve(subcondition_matches.size());
2759             // gather the ids
2760             for (auto& obj : subcondition_matches) {
2761                 if (obj)
2762                     m_subcondition_matches_ids.push_back(obj->ID());
2763             }
2764             // sort them
2765             std::sort(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end());
2766         }
2767 
operator ()Condition::__anone9a56faf1f11::ContainsSimpleMatch2768         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
2769             if (!candidate)
2770                 return false;
2771 
2772             bool match = false;
2773             const auto& candidate_elements = candidate->ContainedObjectIDs(); // guaranteed O(1)
2774 
2775             // We need to test whether candidate_elements and m_subcondition_matches_ids have a common element.
2776             // We choose the strategy that is more efficient by comparing the sizes of both sets.
2777             if (candidate_elements.size() < m_subcondition_matches_ids.size()) {
2778                 // candidate_elements is smaller, so we iterate it and look up each candidate element in m_subcondition_matches_ids
2779                 for (int id : candidate_elements) {
2780                     // std::lower_bound requires m_subcondition_matches_ids to be sorted
2781                     auto matching_it = std::lower_bound(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end(), id);
2782 
2783                     if (matching_it != m_subcondition_matches_ids.end() && *matching_it == id) {
2784                         match = true;
2785                         break;
2786                     }
2787                 }
2788             } else {
2789                 // m_subcondition_matches_ids is smaller, so we iterate it and look up each subcondition match in the set of candidate's elements
2790                 for (int id : m_subcondition_matches_ids) {
2791                     // candidate->Contains() may have a faster implementation than candidate_elements->find()
2792                     if (candidate->Contains(id)) {
2793                         match = true;
2794                         break;
2795                     }
2796                 }
2797             }
2798 
2799             return match;
2800         }
2801 
2802         std::vector<int> m_subcondition_matches_ids;
2803     };
2804 }
2805 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const2806 void Contains::Eval(const ScriptingContext& parent_context,
2807                     ObjectSet& matches, ObjectSet& non_matches,
2808                     SearchDomain search_domain/* = NON_MATCHES*/) const
2809 {
2810     unsigned int search_domain_size = (search_domain == MATCHES ? matches.size() : non_matches.size());
2811     bool simple_eval_safe = parent_context.condition_root_candidate ||
2812                             RootCandidateInvariant() ||
2813                             search_domain_size < 2;
2814     if (!simple_eval_safe) {
2815         // re-evaluate contained objects for each candidate object
2816         Condition::Eval(parent_context, matches, non_matches, search_domain);
2817         return;
2818     }
2819 
2820     // how complicated is this containment test?
2821     if (((search_domain == MATCHES) && matches.empty()) ||
2822         ((search_domain == NON_MATCHES) && non_matches.empty()))
2823     {
2824         // don't need to evaluate anything...
2825 
2826     } else if (search_domain_size == 1) {
2827         // evaluate subcondition on objects contained by the candidate
2828         ScriptingContext local_context(parent_context, search_domain == MATCHES ? *matches.begin() : *non_matches.begin());
2829 
2830         // initialize subcondition candidates from local candidate's contents
2831         const ObjectMap& objects = parent_context.ContextObjects();
2832         ObjectSet subcondition_matches = objects.find(local_context.condition_local_candidate->ContainedObjectIDs());
2833 
2834         // apply subcondition to candidates
2835         if (!subcondition_matches.empty()) {
2836             ObjectSet dummy;
2837             m_condition->Eval(local_context, subcondition_matches, dummy, MATCHES);
2838         }
2839 
2840         // move single local candidate as appropriate...
2841         if (search_domain == MATCHES && subcondition_matches.empty()) {
2842             // move to non_matches
2843             matches.clear();
2844             non_matches.push_back(local_context.condition_local_candidate);
2845         } else if (search_domain == NON_MATCHES && !subcondition_matches.empty()) {
2846             // move to matches
2847             non_matches.clear();
2848             matches.push_back(local_context.condition_local_candidate);
2849         }
2850 
2851     } else {
2852         // evaluate contained objects once using default initial candidates
2853         // of subcondition to find all subcondition matches in the Universe
2854         std::shared_ptr<const UniverseObject> no_object;
2855         ScriptingContext local_context(parent_context, no_object);
2856         ObjectSet subcondition_matches;
2857         m_condition->Eval(local_context, subcondition_matches);
2858 
2859         // check all candidates to see if they contain any subcondition matches
2860         EvalImpl(matches, non_matches, search_domain, ContainsSimpleMatch(subcondition_matches));
2861     }
2862 }
2863 
Description(bool negated) const2864 std::string Contains::Description(bool negated/* = false*/) const {
2865     return str(FlexibleFormat((!negated)
2866         ? UserString("DESC_CONTAINS")
2867         : UserString("DESC_CONTAINS_NOT"))
2868         % m_condition->Description());
2869 }
2870 
Dump(unsigned short ntabs) const2871 std::string Contains::Dump(unsigned short ntabs) const {
2872     std::string retval = DumpIndent(ntabs) + "Contains condition =\n";
2873     retval += m_condition->Dump(ntabs+1);
2874     return retval;
2875 }
2876 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const2877 void Contains::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
2878                                                  ObjectSet& condition_non_targets) const
2879 {
2880     // objects that can contain other objects: systems, fleets, planets
2881     AddSystemSet(parent_context.ContextObjects(), condition_non_targets);
2882     AddFleetSet(parent_context.ContextObjects(), condition_non_targets);
2883     AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
2884 }
2885 
Match(const ScriptingContext & local_context) const2886 bool Contains::Match(const ScriptingContext& local_context) const {
2887     auto candidate = local_context.condition_local_candidate;
2888     if (!candidate) {
2889         ErrorLogger() << "Contains::Match passed no candidate object";
2890         return false;
2891     }
2892 
2893     // get subcondition matches
2894     ObjectSet subcondition_matches;
2895     m_condition->Eval(local_context, subcondition_matches);
2896 
2897     // does candidate object contain any subcondition matches?
2898     for (auto& obj : subcondition_matches)
2899         if (candidate->Contains(obj->ID()))
2900             return true;
2901 
2902     return false;
2903 }
2904 
SetTopLevelContent(const std::string & content_name)2905 void Contains::SetTopLevelContent(const std::string& content_name) {
2906     if (m_condition)
2907         m_condition->SetTopLevelContent(content_name);
2908 }
2909 
GetCheckSum() const2910 unsigned int Contains::GetCheckSum() const {
2911     unsigned int retval{0};
2912 
2913     CheckSums::CheckSumCombine(retval, "Condition::Contains");
2914     CheckSums::CheckSumCombine(retval, m_condition);
2915 
2916     TraceLogger() << "GetCheckSum(Contains): retval: " << retval;
2917     return retval;
2918 }
2919 
2920 ///////////////////////////////////////////////////////////
2921 // ContainedBy                                           //
2922 ///////////////////////////////////////////////////////////
ContainedBy(std::unique_ptr<Condition> && condition)2923 ContainedBy::ContainedBy(std::unique_ptr<Condition>&& condition) :
2924     Condition(),
2925     m_condition(std::move(condition))
2926 {
2927     m_root_candidate_invariant = m_condition->RootCandidateInvariant();
2928     m_target_invariant = m_condition->TargetInvariant();
2929     m_source_invariant = m_condition->SourceInvariant();
2930 }
2931 
operator ==(const Condition & rhs) const2932 bool ContainedBy::operator==(const Condition& rhs) const {
2933     if (this == &rhs)
2934         return true;
2935     if (typeid(*this) != typeid(rhs))
2936         return false;
2937 
2938     const ContainedBy& rhs_ = static_cast<const ContainedBy&>(rhs);
2939 
2940     CHECK_COND_VREF_MEMBER(m_condition)
2941 
2942     return true;
2943 }
2944 
2945 namespace {
2946     struct ContainedBySimpleMatch {
ContainedBySimpleMatchCondition::__anone9a56faf2011::ContainedBySimpleMatch2947         ContainedBySimpleMatch(const ObjectSet& subcondition_matches) :
2948             m_subcondition_matches_ids()
2949         {
2950             // We need a sorted container for efficiently intersecting
2951             // subcondition_matches with the set of objects containing some
2952             // candidate object.
2953             // We only need ids, not objects, so we can do that conversion
2954             // here as well, simplifying later code.
2955             // Note that this constructor is called only once per
2956             // ContainedBy::Eval(), its work cannot help performance when
2957             // executed for each candidate.
2958             m_subcondition_matches_ids.reserve(subcondition_matches.size());
2959             // gather the ids
2960             for (auto& obj : subcondition_matches) {
2961                 if (obj)
2962                 { m_subcondition_matches_ids.push_back(obj->ID()); }
2963             }
2964             // sort them
2965             std::sort(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end());
2966         }
2967 
operator ()Condition::__anone9a56faf2011::ContainedBySimpleMatch2968         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
2969             if (!candidate)
2970                 return false;
2971 
2972             bool match = false;
2973             // gather the objects containing candidate
2974             std::vector<int> candidate_containers;
2975             const int candidate_id = candidate->ID();
2976             const int    system_id = candidate->SystemID();
2977             const int container_id = candidate->ContainerObjectID();
2978             if (   system_id != INVALID_OBJECT_ID &&    system_id != candidate_id) candidate_containers.push_back(   system_id);
2979             if (container_id != INVALID_OBJECT_ID && container_id !=    system_id) candidate_containers.push_back(container_id);
2980             // FIXME: currently, direct container and system will do. In the future, we might need a way to retrieve containers of containers
2981 
2982             // We need to test whether candidate_containers and m_subcondition_matches_ids have a common element.
2983             // We choose the strategy that is more efficient by comparing the sizes of both sets.
2984             if (candidate_containers.size() < m_subcondition_matches_ids.size()) {
2985                 // candidate_containers is smaller, so we iterate it and look up each candidate container in m_subcondition_matches_ids
2986                 for (int id : candidate_containers) {
2987                     // std::lower_bound requires m_subcondition_matches_ids to be sorted
2988                     auto matching_it = std::lower_bound(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end(), id);
2989 
2990                     if (matching_it != m_subcondition_matches_ids.end() && *matching_it == id) {
2991                         match = true;
2992                         break;
2993                     }
2994                 }
2995             } else {
2996                 // m_subcondition_matches_ids is smaller, so we iterate it and look up each subcondition match in the set of candidate's containers
2997                 for (int id : m_subcondition_matches_ids) {
2998                     // candidate->ContainedBy() may have a faster implementation than candidate_containers->find()
2999                     if (candidate->ContainedBy(id)) {
3000                         match = true;
3001                         break;
3002                     }
3003                 }
3004             }
3005 
3006             return match;
3007         }
3008 
3009         std::vector<int> m_subcondition_matches_ids;
3010     };
3011 }
3012 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const3013 void ContainedBy::Eval(const ScriptingContext& parent_context,
3014                        ObjectSet& matches, ObjectSet& non_matches,
3015                        SearchDomain search_domain/* = NON_MATCHES*/) const
3016 {
3017     unsigned int search_domain_size = (search_domain == MATCHES ? matches.size() : non_matches.size());
3018     bool simple_eval_safe = parent_context.condition_root_candidate ||
3019                             RootCandidateInvariant() ||
3020                             search_domain_size < 2;
3021 
3022     if (!simple_eval_safe) {
3023         // re-evaluate container objects for each candidate object
3024         Condition::Eval(parent_context, matches, non_matches, search_domain);
3025         return;
3026     }
3027 
3028     // how complicated is this containment test?
3029     if (((search_domain == MATCHES) && matches.empty()) ||
3030         ((search_domain == NON_MATCHES) && non_matches.empty()))
3031     {
3032         // don't need to evaluate anything...
3033 
3034     } else if (search_domain_size == 1) {
3035         // evaluate subcondition on objects that contain the candidate
3036         ScriptingContext local_context(parent_context, search_domain == MATCHES ? *matches.begin() : *non_matches.begin());
3037 
3038         // initialize subcondition candidates from local candidate's containers
3039         const ObjectMap& objects = parent_context.ContextObjects();
3040         std::set<int> container_object_ids;
3041         if (local_context.condition_local_candidate->ContainerObjectID() != INVALID_OBJECT_ID)
3042             container_object_ids.insert(local_context.condition_local_candidate->ContainerObjectID());
3043         if (local_context.condition_local_candidate->SystemID() != INVALID_OBJECT_ID)
3044             container_object_ids.insert(local_context.condition_local_candidate->SystemID());
3045 
3046         ObjectSet subcondition_matches = objects.find(container_object_ids);
3047 
3048         // apply subcondition to candidates
3049         if (!subcondition_matches.empty()) {
3050             ObjectSet dummy;
3051             m_condition->Eval(local_context, subcondition_matches, dummy, MATCHES);
3052         }
3053 
3054         // move single local candidate as appropriate...
3055         if (search_domain == MATCHES && subcondition_matches.empty()) {
3056             // move to non_matches
3057             matches.clear();
3058             non_matches.push_back(local_context.condition_local_candidate);
3059         } else if (search_domain == NON_MATCHES && !subcondition_matches.empty()) {
3060             // move to matches
3061             non_matches.clear();
3062             matches.push_back(local_context.condition_local_candidate);
3063         }
3064 
3065     } else {
3066         // evaluate container objects once using default initial candidates
3067         // of subcondition to find all subcondition matches in the Universe
3068         std::shared_ptr<const UniverseObject> no_object;
3069         ScriptingContext local_context(parent_context, no_object);
3070         ObjectSet subcondition_matches;
3071         m_condition->Eval(local_context, subcondition_matches);
3072 
3073         // check all candidates to see if they contain any subcondition matches
3074         EvalImpl(matches, non_matches, search_domain, ContainedBySimpleMatch(subcondition_matches));
3075     }
3076 }
3077 
Description(bool negated) const3078 std::string ContainedBy::Description(bool negated/* = false*/) const {
3079     return str(FlexibleFormat((!negated)
3080         ? UserString("DESC_CONTAINED_BY")
3081         : UserString("DESC_CONTAINED_BY_NOT"))
3082         % m_condition->Description());
3083 }
3084 
Dump(unsigned short ntabs) const3085 std::string ContainedBy::Dump(unsigned short ntabs) const {
3086     std::string retval = DumpIndent(ntabs) + "ContainedBy condition =\n";
3087     retval += m_condition->Dump(ntabs+1);
3088     return retval;
3089 }
3090 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const3091 void ContainedBy::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
3092                                                     ObjectSet& condition_non_targets) const
3093 {
3094     // objects that can be contained by other objects: fleets, planets, ships, buildings
3095     AddFleetSet(parent_context.ContextObjects(), condition_non_targets);
3096     AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
3097     AddShipSet(parent_context.ContextObjects(), condition_non_targets);
3098     AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
3099 }
3100 
Match(const ScriptingContext & local_context) const3101 bool ContainedBy::Match(const ScriptingContext& local_context) const {
3102     auto candidate = local_context.condition_local_candidate;
3103     if (!candidate) {
3104         ErrorLogger() << "ContainedBy::Match passed no candidate object";
3105         return false;
3106     }
3107 
3108     // get containing objects
3109     std::set<int> containers;
3110     if (candidate->SystemID() != INVALID_OBJECT_ID)
3111         containers.insert(candidate->SystemID());
3112     if (candidate->ContainerObjectID() != INVALID_OBJECT_ID && candidate->ContainerObjectID() != candidate->SystemID())
3113         containers.insert(candidate->ContainerObjectID());
3114 
3115     ObjectSet container_objects = local_context.ContextObjects().find<const UniverseObject>(containers);
3116     if (container_objects.empty())
3117         return false;
3118 
3119     m_condition->Eval(local_context, container_objects);
3120 
3121     return container_objects.empty();
3122 }
3123 
SetTopLevelContent(const std::string & content_name)3124 void ContainedBy::SetTopLevelContent(const std::string& content_name) {
3125     if (m_condition)
3126         m_condition->SetTopLevelContent(content_name);
3127 }
3128 
GetCheckSum() const3129 unsigned int ContainedBy::GetCheckSum() const {
3130     unsigned int retval{0};
3131 
3132     CheckSums::CheckSumCombine(retval, "Condition::ContainedBy");
3133     CheckSums::CheckSumCombine(retval, m_condition);
3134 
3135     TraceLogger() << "GetCheckSum(ContainedBy): retval: " << retval;
3136     return retval;
3137 }
3138 
3139 ///////////////////////////////////////////////////////////
3140 // InOrIsSystem                                          //
3141 ///////////////////////////////////////////////////////////
InOrIsSystem(std::unique_ptr<ValueRef::ValueRef<int>> && system_id)3142 InOrIsSystem::InOrIsSystem(std::unique_ptr<ValueRef::ValueRef<int>>&& system_id) :
3143     Condition(),
3144     m_system_id(std::move(system_id))
3145 {
3146     m_root_candidate_invariant = !m_system_id || m_system_id->RootCandidateInvariant();
3147     m_target_invariant = !m_system_id || m_system_id->TargetInvariant();
3148     m_source_invariant = !m_system_id || m_system_id->SourceInvariant();
3149 }
3150 
operator ==(const Condition & rhs) const3151 bool InOrIsSystem::operator==(const Condition& rhs) const {
3152     if (this == &rhs)
3153         return true;
3154     if (typeid(*this) != typeid(rhs))
3155         return false;
3156 
3157     const InOrIsSystem& rhs_ = static_cast<const InOrIsSystem&>(rhs);
3158 
3159     CHECK_COND_VREF_MEMBER(m_system_id)
3160 
3161     return true;
3162 }
3163 
3164 namespace {
3165     struct InSystemSimpleMatch {
InSystemSimpleMatchCondition::__anone9a56faf2111::InSystemSimpleMatch3166         InSystemSimpleMatch(int system_id) :
3167             m_system_id(system_id)
3168         {}
3169 
operator ()Condition::__anone9a56faf2111::InSystemSimpleMatch3170         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
3171             if (!candidate)
3172                 return false;
3173             if (m_system_id == INVALID_OBJECT_ID)
3174                 return candidate->SystemID() != INVALID_OBJECT_ID;  // match objects in any system (including any system itself)
3175             else
3176                 return candidate->SystemID() == m_system_id;        // match objects in specified system (including that system itself)
3177         }
3178 
3179         int m_system_id;
3180     };
3181 }
3182 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const3183 void InOrIsSystem::Eval(const ScriptingContext& parent_context,
3184                         ObjectSet& matches, ObjectSet& non_matches,
3185                         SearchDomain search_domain/* = NON_MATCHES*/) const
3186 {
3187     bool simple_eval_safe = !m_system_id || m_system_id->ConstantExpr() ||
3188                             (m_system_id->LocalCandidateInvariant() &&
3189                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
3190     if (simple_eval_safe) {
3191         // evaluate system id once, and use to check all candidate objects
3192         int system_id = (m_system_id ? m_system_id->Eval(parent_context) : INVALID_OBJECT_ID);
3193         EvalImpl(matches, non_matches, search_domain, InSystemSimpleMatch(system_id));
3194     } else {
3195         // re-evaluate empire id for each candidate object
3196         Condition::Eval(parent_context, matches, non_matches, search_domain);
3197     }
3198 }
3199 
Description(bool negated) const3200 std::string InOrIsSystem::Description(bool negated/* = false*/) const {
3201     std::string system_str;
3202     int system_id = INVALID_OBJECT_ID;
3203     if (m_system_id && m_system_id->ConstantExpr())
3204         system_id = m_system_id->Eval();
3205     if (auto system = Objects().get<System>(system_id))
3206         system_str = system->Name();
3207     else if (m_system_id)
3208         system_str = m_system_id->Description();
3209 
3210     std::string description_str;
3211     if (!system_str.empty())
3212         description_str = (!negated)
3213             ? UserString("DESC_IN_SYSTEM")
3214             : UserString("DESC_IN_SYSTEM_NOT");
3215     else
3216         description_str = (!negated)
3217             ? UserString("DESC_IN_SYSTEM_SIMPLE")
3218             : UserString("DESC_IN_SYSTEM_SIMPLE_NOT");
3219 
3220     return str(FlexibleFormat(description_str) % system_str);
3221 }
3222 
Dump(unsigned short ntabs) const3223 std::string InOrIsSystem::Dump(unsigned short ntabs) const {
3224     std::string retval = DumpIndent(ntabs) + "InSystem";
3225     if (m_system_id)
3226         retval += " id = " + m_system_id->Dump(ntabs);
3227     retval += "\n";
3228     return retval;
3229 }
3230 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const3231 void InOrIsSystem::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
3232                                                      ObjectSet& condition_non_targets) const
3233 {
3234     if (!m_system_id) {
3235         // can match objects in any system, or any system
3236         AddAllObjectsSet(parent_context.ContextObjects(), condition_non_targets);
3237         return;
3238     }
3239 
3240     bool simple_eval_safe = m_system_id->ConstantExpr() ||
3241                             (m_system_id->LocalCandidateInvariant() &&
3242                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
3243 
3244     if (!simple_eval_safe) {
3245         // almost anything can be in a system, and can also match the system itself
3246         AddAllObjectsSet(parent_context.ContextObjects(), condition_non_targets);
3247         return;
3248     }
3249 
3250     // simple case of a single specified system id; can add just objects in that system
3251     int system_id = m_system_id->Eval(parent_context);
3252     auto system = parent_context.ContextObjects().get<System>(system_id);
3253     if (!system)
3254         return;
3255 
3256     const std::set<int>& system_object_ids = system->ObjectIDs();
3257     auto sys_objs = parent_context.ContextObjects().find(system_object_ids);
3258 
3259     // insert all objects that have the specified system id
3260     condition_non_targets.reserve(sys_objs.size() + 1);
3261     std::copy(sys_objs.begin(), sys_objs.end(), std::back_inserter(condition_non_targets));
3262     // also insert system itself
3263     condition_non_targets.push_back(system);
3264 }
3265 
Match(const ScriptingContext & local_context) const3266 bool InOrIsSystem::Match(const ScriptingContext& local_context) const {
3267     auto candidate = local_context.condition_local_candidate;
3268     if (!candidate) {
3269         ErrorLogger() << "InOrIsSystem::Match passed no candidate object";
3270         return false;
3271     }
3272     int system_id = (m_system_id ? m_system_id->Eval(local_context) : INVALID_OBJECT_ID);
3273     return InSystemSimpleMatch(system_id)(candidate);
3274 }
3275 
SetTopLevelContent(const std::string & content_name)3276 void InOrIsSystem::SetTopLevelContent(const std::string& content_name) {
3277     if (m_system_id)
3278         m_system_id->SetTopLevelContent(content_name);
3279 }
3280 
GetCheckSum() const3281 unsigned int InOrIsSystem::GetCheckSum() const {
3282     unsigned int retval{0};
3283 
3284     CheckSums::CheckSumCombine(retval, "Condition::InOrIsSystem");
3285     CheckSums::CheckSumCombine(retval, m_system_id);
3286 
3287     TraceLogger() << "GetCheckSum(InOrIsSystem): retval: " << retval;
3288     return retval;
3289 }
3290 
3291 ///////////////////////////////////////////////////////////
3292 // OnPlanet                                              //
3293 ///////////////////////////////////////////////////////////
OnPlanet(std::unique_ptr<ValueRef::ValueRef<int>> && planet_id)3294 OnPlanet::OnPlanet(std::unique_ptr<ValueRef::ValueRef<int>>&& planet_id) :
3295     Condition(),
3296     m_planet_id(std::move(planet_id))
3297 {
3298     m_root_candidate_invariant = !m_planet_id || m_planet_id->RootCandidateInvariant();
3299     m_target_invariant = !m_planet_id || m_planet_id->TargetInvariant();
3300     m_source_invariant = !m_planet_id || m_planet_id->SourceInvariant();
3301 }
3302 
operator ==(const Condition & rhs) const3303 bool OnPlanet::operator==(const Condition& rhs) const {
3304     if (this == &rhs)
3305         return true;
3306     if (typeid(*this) != typeid(rhs))
3307         return false;
3308 
3309     const OnPlanet& rhs_ = static_cast<const OnPlanet&>(rhs);
3310 
3311     CHECK_COND_VREF_MEMBER(m_planet_id)
3312 
3313     return true;
3314 }
3315 
3316 namespace {
3317     struct OnPlanetSimpleMatch {
OnPlanetSimpleMatchCondition::__anone9a56faf2211::OnPlanetSimpleMatch3318         OnPlanetSimpleMatch(int planet_id) :
3319             m_planet_id(planet_id)
3320         {}
3321 
operator ()Condition::__anone9a56faf2211::OnPlanetSimpleMatch3322         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
3323             if (!candidate)
3324                 return false;
3325             auto building = std::dynamic_pointer_cast<const ::Building>(candidate);
3326             if (!building)
3327                 return false;
3328 
3329             if (m_planet_id == INVALID_OBJECT_ID)
3330                 return building->PlanetID() != INVALID_OBJECT_ID;  // match objects on any planet
3331             else
3332                 return building->PlanetID() == m_planet_id;        // match objects on specified planet
3333         }
3334 
3335         int m_planet_id;
3336     };
3337 }
3338 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const3339 void OnPlanet::Eval(const ScriptingContext& parent_context,
3340                     ObjectSet& matches, ObjectSet& non_matches,
3341                     SearchDomain search_domain/* = NON_MATCHES*/) const
3342 {
3343     bool simple_eval_safe = !m_planet_id || m_planet_id->ConstantExpr() ||
3344                             (m_planet_id->LocalCandidateInvariant() &&
3345                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
3346     if (simple_eval_safe) {
3347         // evaluate planet id once, and use to check all candidate objects
3348         int planet_id = (m_planet_id ? m_planet_id->Eval(parent_context) : INVALID_OBJECT_ID);
3349         EvalImpl(matches, non_matches, search_domain, OnPlanetSimpleMatch(planet_id));
3350     } else {
3351         // re-evaluate empire id for each candidate object
3352         Condition::Eval(parent_context, matches, non_matches, search_domain);
3353     }
3354 }
3355 
Description(bool negated) const3356 std::string OnPlanet::Description(bool negated/* = false*/) const {
3357     std::string planet_str;
3358     int planet_id = INVALID_OBJECT_ID;
3359     if (m_planet_id && m_planet_id->ConstantExpr())
3360         planet_id = m_planet_id->Eval();
3361     if (auto planet = Objects().get<Planet>(planet_id))
3362         planet_str = planet->Name();
3363     else if (m_planet_id)
3364         planet_str = m_planet_id->Description();
3365 
3366     std::string description_str;
3367     if (!planet_str.empty())
3368         description_str = (!negated)
3369             ? UserString("DESC_ON_PLANET")
3370             : UserString("DESC_ON_PLANET_NOT");
3371     else
3372         description_str = (!negated)
3373             ? UserString("DESC_ON_PLANET_SIMPLE")
3374             : UserString("DESC_ON_PLANET_SIMPLE_NOT");
3375 
3376     return str(FlexibleFormat(description_str) % planet_str);
3377 }
3378 
Dump(unsigned short ntabs) const3379 std::string OnPlanet::Dump(unsigned short ntabs) const {
3380     std::string retval = DumpIndent(ntabs) + "OnPlanet";
3381     if (m_planet_id)
3382         retval += " id = " + m_planet_id->Dump(ntabs);
3383     retval += "\n";
3384     return retval;
3385 }
3386 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const3387 void OnPlanet::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
3388                                                  ObjectSet& condition_non_targets) const
3389 {
3390     if (!m_planet_id) {
3391         // only buildings can be on planets
3392         AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
3393         return;
3394     }
3395 
3396     bool simple_eval_safe = m_planet_id->ConstantExpr() ||
3397                             (m_planet_id->LocalCandidateInvariant() &&
3398                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
3399 
3400     if (!simple_eval_safe) {
3401         // only buildings can be on planets
3402         AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
3403         return;
3404     }
3405 
3406     // simple case of a single specified system id; can add just objects in that system
3407     int planet_id = m_planet_id->Eval(parent_context);
3408     auto planet = parent_context.ContextObjects().get<Planet>(planet_id);
3409     if (!planet)
3410         return;
3411 
3412     // insert all objects that have the specified planet id
3413     condition_non_targets = parent_context.ContextObjects().find(planet->BuildingIDs());
3414 }
3415 
Match(const ScriptingContext & local_context) const3416 bool OnPlanet::Match(const ScriptingContext& local_context) const {
3417     auto candidate = local_context.condition_local_candidate;
3418     if (!candidate) {
3419         ErrorLogger() << "OnPlanet::Match passed no candidate object";
3420         return false;
3421     }
3422     int planet_id = (m_planet_id ? m_planet_id->Eval(local_context) : INVALID_OBJECT_ID);
3423     return OnPlanetSimpleMatch(planet_id)(candidate);
3424 }
3425 
SetTopLevelContent(const std::string & content_name)3426 void OnPlanet::SetTopLevelContent(const std::string& content_name) {
3427     if (m_planet_id)
3428         m_planet_id->SetTopLevelContent(content_name);
3429 }
3430 
GetCheckSum() const3431 unsigned int OnPlanet::GetCheckSum() const {
3432     unsigned int retval{0};
3433 
3434     CheckSums::CheckSumCombine(retval, "Condition::OnPlanet");
3435     CheckSums::CheckSumCombine(retval, m_planet_id);
3436 
3437     TraceLogger() << "GetCheckSum(OnPlanet): retval: " << retval;
3438     return retval;
3439 }
3440 
3441 ///////////////////////////////////////////////////////////
3442 // ObjectID                                              //
3443 ///////////////////////////////////////////////////////////
ObjectID(std::unique_ptr<ValueRef::ValueRef<int>> && object_id)3444 ObjectID::ObjectID(std::unique_ptr<ValueRef::ValueRef<int>>&& object_id) :
3445     Condition(),
3446     m_object_id(std::move(object_id))
3447 {
3448     m_root_candidate_invariant = !m_object_id || m_object_id->RootCandidateInvariant();
3449     m_target_invariant = !m_object_id || m_object_id->TargetInvariant();
3450     m_source_invariant = !m_object_id || m_object_id->SourceInvariant();
3451 }
3452 
operator ==(const Condition & rhs) const3453 bool ObjectID::operator==(const Condition& rhs) const {
3454     if (this == &rhs)
3455         return true;
3456     if (typeid(*this) != typeid(rhs))
3457         return false;
3458 
3459     const ObjectID& rhs_ = static_cast<const ObjectID&>(rhs);
3460 
3461     CHECK_COND_VREF_MEMBER(m_object_id)
3462 
3463     return true;
3464 }
3465 
3466 namespace {
3467     struct ObjectIDSimpleMatch {
ObjectIDSimpleMatchCondition::__anone9a56faf2311::ObjectIDSimpleMatch3468         ObjectIDSimpleMatch(int object_id) :
3469             m_object_id(object_id)
3470         {}
3471 
operator ()Condition::__anone9a56faf2311::ObjectIDSimpleMatch3472         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
3473             return candidate &&
3474                 m_object_id != INVALID_OBJECT_ID &&
3475                 candidate->ID() == m_object_id;
3476         }
3477 
3478         int m_object_id;
3479     };
3480 }
3481 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const3482 void ObjectID::Eval(const ScriptingContext& parent_context,
3483                     ObjectSet& matches, ObjectSet& non_matches,
3484                     SearchDomain search_domain/* = NON_MATCHES*/) const
3485 {
3486     bool simple_eval_safe = !m_object_id || m_object_id->ConstantExpr() ||
3487                             (m_object_id->LocalCandidateInvariant() &&
3488                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
3489     if (simple_eval_safe) {
3490         // evaluate empire id once, and use to check all candidate objects
3491         int object_id = (m_object_id ? m_object_id->Eval(parent_context) : INVALID_OBJECT_ID);
3492         EvalImpl(matches, non_matches, search_domain, ObjectIDSimpleMatch(object_id));
3493     } else {
3494         // re-evaluate empire id for each candidate object
3495         Condition::Eval(parent_context, matches, non_matches, search_domain);
3496     }
3497 }
3498 
Description(bool negated) const3499 std::string ObjectID::Description(bool negated/* = false*/) const {
3500     std::string object_str;
3501     int object_id = INVALID_OBJECT_ID;
3502     if (m_object_id && m_object_id->ConstantExpr())
3503         object_id = m_object_id->Eval();
3504     if (auto system = Objects().get<System>(object_id))
3505         object_str = system->Name();
3506     else if (m_object_id)
3507         object_str = m_object_id->Description();
3508     else
3509         object_str = UserString("ERROR");   // should always have a valid ID for this condition
3510 
3511     return str(FlexibleFormat((!negated)
3512         ? UserString("DESC_OBJECT_ID")
3513         : UserString("DESC_OBJECT_ID_NOT"))
3514                % object_str);
3515 }
3516 
Dump(unsigned short ntabs) const3517 std::string ObjectID::Dump(unsigned short ntabs) const
3518 { return DumpIndent(ntabs) + "Object id = " + m_object_id->Dump(ntabs) + "\n"; }
3519 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const3520 void ObjectID::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
3521                                                  ObjectSet& condition_non_targets) const
3522 {
3523     if (!m_object_id)
3524         return;
3525 
3526     bool simple_eval_safe = m_object_id->ConstantExpr() ||
3527                             (m_object_id->LocalCandidateInvariant() &&
3528                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
3529 
3530     if (!simple_eval_safe) {
3531         AddAllObjectsSet(parent_context.ContextObjects(), condition_non_targets);
3532         return;
3533     }
3534 
3535     // simple case of a single specified id; can add just that object
3536     int object_id = m_object_id->Eval(parent_context);
3537     if (object_id == INVALID_OBJECT_ID)
3538         return;
3539 
3540     if (auto obj = parent_context.ContextObjects().ExistingObject(object_id))
3541         condition_non_targets.push_back(obj);
3542 }
3543 
Match(const ScriptingContext & local_context) const3544 bool ObjectID::Match(const ScriptingContext& local_context) const {
3545     auto candidate = local_context.condition_local_candidate;
3546     if (!candidate) {
3547         ErrorLogger() << "ObjectID::Match passed no candidate object";
3548         return false;
3549     }
3550 
3551     return ObjectIDSimpleMatch(m_object_id->Eval(local_context))(candidate);
3552 }
3553 
SetTopLevelContent(const std::string & content_name)3554 void ObjectID::SetTopLevelContent(const std::string& content_name) {
3555     if (m_object_id)
3556         m_object_id->SetTopLevelContent(content_name);
3557 }
3558 
GetCheckSum() const3559 unsigned int ObjectID::GetCheckSum() const {
3560     unsigned int retval{0};
3561 
3562     CheckSums::CheckSumCombine(retval, "Condition::ObjectID");
3563     CheckSums::CheckSumCombine(retval, m_object_id);
3564 
3565     TraceLogger() << "GetCheckSum(ObjectID): retval: " << retval;
3566     return retval;
3567 }
3568 
3569 ///////////////////////////////////////////////////////////
3570 // PlanetType                                            //
3571 ///////////////////////////////////////////////////////////
PlanetType(std::vector<std::unique_ptr<ValueRef::ValueRef<::PlanetType>>> && types)3572 PlanetType::PlanetType(std::vector<std::unique_ptr<ValueRef::ValueRef< ::PlanetType>>>&& types) :
3573     Condition(),
3574     m_types(std::move(types))
3575 {
3576     m_root_candidate_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->RootCandidateInvariant(); });
3577     m_target_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->TargetInvariant(); });
3578     m_source_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->SourceInvariant(); });
3579 }
3580 
operator ==(const Condition & rhs) const3581 bool PlanetType::operator==(const Condition& rhs) const {
3582     if (this == &rhs)
3583         return true;
3584     if (typeid(*this) != typeid(rhs))
3585         return false;
3586 
3587     const PlanetType& rhs_ = static_cast<const PlanetType&>(rhs);
3588 
3589     if (m_types.size() != rhs_.m_types.size())
3590         return false;
3591     for (unsigned int i = 0; i < m_types.size(); ++i) {
3592         CHECK_COND_VREF_MEMBER(m_types.at(i))
3593     }
3594 
3595     return true;
3596 }
3597 
3598 namespace {
3599     struct PlanetTypeSimpleMatch {
PlanetTypeSimpleMatchCondition::__anone9a56faf2711::PlanetTypeSimpleMatch3600         PlanetTypeSimpleMatch(const std::vector< ::PlanetType>& types, const ObjectMap& objects) :
3601             m_types(types),
3602             m_objects(objects)
3603         {}
3604 
operator ()Condition::__anone9a56faf2711::PlanetTypeSimpleMatch3605         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
3606             if (!candidate)
3607                 return false;
3608 
3609             // is it a planet or on a planet?
3610             auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
3611             std::shared_ptr<const ::Building> building;
3612             if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
3613                 planet = m_objects.get<Planet>(building->PlanetID());
3614             if (planet) {
3615                 // is it one of the specified building types?
3616                 return std::count(m_types.begin(), m_types.end(), planet->Type());
3617             }
3618 
3619             return false;
3620         }
3621 
3622         const std::vector< ::PlanetType>& m_types;
3623         const ObjectMap& m_objects;
3624     };
3625 }
3626 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const3627 void PlanetType::Eval(const ScriptingContext& parent_context,
3628                       ObjectSet& matches, ObjectSet& non_matches,
3629                       SearchDomain search_domain/* = NON_MATCHES*/) const
3630 {
3631     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
3632     if (simple_eval_safe) {
3633         // check each valueref for invariance to local candidate
3634         for (auto& type : m_types) {
3635             if (!type->LocalCandidateInvariant()) {
3636                 simple_eval_safe = false;
3637                 break;
3638             }
3639         }
3640     }
3641     if (simple_eval_safe) {
3642         // evaluate types once, and use to check all candidate objects
3643         std::vector< ::PlanetType> types;
3644         // get all types from valuerefs
3645         for (auto& type : m_types) {
3646             types.push_back(type->Eval(parent_context));
3647         }
3648         EvalImpl(matches, non_matches, search_domain, PlanetTypeSimpleMatch(types, parent_context.ContextObjects()));
3649     } else {
3650         // re-evaluate contained objects for each candidate object
3651         Condition::Eval(parent_context, matches, non_matches, search_domain);
3652     }
3653 }
3654 
Description(bool negated) const3655 std::string PlanetType::Description(bool negated/* = false*/) const {
3656     std::string values_str;
3657     for (unsigned int i = 0; i < m_types.size(); ++i) {
3658         values_str += m_types[i]->ConstantExpr() ?
3659                         UserString(boost::lexical_cast<std::string>(m_types[i]->Eval())) :
3660                         m_types[i]->Description();
3661         if (2 <= m_types.size() && i < m_types.size() - 2) {
3662             values_str += ", ";
3663         } else if (i == m_types.size() - 2) {
3664             values_str += m_types.size() < 3 ? " " : ", ";
3665             values_str += UserString("OR");
3666             values_str += " ";
3667         }
3668     }
3669     return str(FlexibleFormat((!negated)
3670         ? UserString("DESC_PLANET_TYPE")
3671         : UserString("DESC_PLANET_TYPE_NOT"))
3672         % values_str);
3673 }
3674 
Dump(unsigned short ntabs) const3675 std::string PlanetType::Dump(unsigned short ntabs) const {
3676     std::string retval = DumpIndent(ntabs) + "Planet type = ";
3677     if (m_types.size() == 1) {
3678         retval += m_types[0]->Dump(ntabs) + "\n";
3679     } else {
3680         retval += "[ ";
3681         for (auto& type : m_types) {
3682             retval += type->Dump(ntabs) + " ";
3683         }
3684         retval += "]\n";
3685     }
3686     return retval;
3687 }
3688 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const3689 void PlanetType::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
3690                                                    ObjectSet& condition_non_targets) const
3691 {
3692     AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
3693     AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
3694 }
3695 
Match(const ScriptingContext & local_context) const3696 bool PlanetType::Match(const ScriptingContext& local_context) const {
3697     auto candidate = local_context.condition_local_candidate;
3698     if (!candidate) {
3699         ErrorLogger() << "PlanetType::Match passed no candidate object";
3700         return false;
3701     }
3702 
3703     auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
3704     std::shared_ptr<const ::Building> building;
3705     if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
3706         planet = local_context.ContextObjects().get<Planet>(building->PlanetID());
3707 
3708     if (planet) {
3709         for (auto& type : m_types) {
3710             if (type->Eval(local_context) == planet->Type())
3711                 return true;
3712         }
3713     }
3714     return false;
3715 }
3716 
SetTopLevelContent(const std::string & content_name)3717 void PlanetType::SetTopLevelContent(const std::string& content_name) {
3718     for (auto& type : m_types) {
3719         if (type)
3720             type->SetTopLevelContent(content_name);
3721     }
3722 }
3723 
GetCheckSum() const3724 unsigned int PlanetType::GetCheckSum() const {
3725     unsigned int retval{0};
3726 
3727     CheckSums::CheckSumCombine(retval, "Condition::PlanetType");
3728     CheckSums::CheckSumCombine(retval, m_types);
3729 
3730     TraceLogger() << "GetCheckSum(PlanetType): retval: " << retval;
3731     return retval;
3732 }
3733 
3734 ///////////////////////////////////////////////////////////
3735 // PlanetSize                                            //
3736 ///////////////////////////////////////////////////////////
PlanetSize(std::vector<std::unique_ptr<ValueRef::ValueRef<::PlanetSize>>> && sizes)3737 PlanetSize::PlanetSize(std::vector<std::unique_ptr<ValueRef::ValueRef< ::PlanetSize>>>&& sizes) :
3738     Condition(),
3739     m_sizes(std::move(sizes))
3740 {
3741     m_root_candidate_invariant = boost::algorithm::all_of(m_sizes, [](auto& e){ return e->RootCandidateInvariant(); });
3742     m_target_invariant = boost::algorithm::all_of(m_sizes, [](auto& e){ return e->TargetInvariant(); });
3743     m_source_invariant = boost::algorithm::all_of(m_sizes, [](auto& e){ return e->SourceInvariant(); });
3744 }
3745 
operator ==(const Condition & rhs) const3746 bool PlanetSize::operator==(const Condition& rhs) const {
3747     if (this == &rhs)
3748         return true;
3749     if (typeid(*this) != typeid(rhs))
3750         return false;
3751 
3752     const PlanetSize& rhs_ = static_cast<const PlanetSize&>(rhs);
3753 
3754     if (m_sizes.size() != rhs_.m_sizes.size())
3755         return false;
3756     for (unsigned int i = 0; i < m_sizes.size(); ++i) {
3757         CHECK_COND_VREF_MEMBER(m_sizes.at(i))
3758     }
3759 
3760     return true;
3761 }
3762 
3763 namespace {
3764     struct PlanetSizeSimpleMatch {
PlanetSizeSimpleMatchCondition::__anone9a56faf2b11::PlanetSizeSimpleMatch3765         PlanetSizeSimpleMatch(const std::vector< ::PlanetSize>& sizes, const ObjectMap& objects) :
3766             m_sizes(sizes),
3767             m_objects(objects)
3768         {}
3769 
operator ()Condition::__anone9a56faf2b11::PlanetSizeSimpleMatch3770         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
3771             if (!candidate)
3772                 return false;
3773 
3774             // is it a planet or on a planet? TODO: This concept should be generalized and factored out.
3775             auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
3776             std::shared_ptr<const ::Building> building;
3777             if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
3778                 planet = m_objects.get<Planet>(building->PlanetID());
3779             if (planet) {
3780                 // is it one of the specified building types?
3781                 for (auto size : m_sizes) {
3782                     if (planet->Size() == size)
3783                         return true;
3784                 }
3785             }
3786 
3787             return false;
3788         }
3789 
3790         const std::vector< ::PlanetSize>& m_sizes;
3791         const ObjectMap& m_objects;
3792     };
3793 }
3794 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const3795 void PlanetSize::Eval(const ScriptingContext& parent_context,
3796                                  ObjectSet& matches, ObjectSet& non_matches,
3797                                  SearchDomain search_domain/* = NON_MATCHES*/) const
3798 {
3799     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
3800     if (simple_eval_safe) {
3801         // check each valueref for invariance to local candidate
3802         for (auto& size : m_sizes) {
3803             if (!size->LocalCandidateInvariant()) {
3804                 simple_eval_safe = false;
3805                 break;
3806             }
3807         }
3808     }
3809     if (simple_eval_safe) {
3810         // evaluate types once, and use to check all candidate objects
3811         std::vector< ::PlanetSize> sizes;
3812         // get all types from valuerefs
3813         for (auto& size : m_sizes) {
3814             sizes.push_back(size->Eval(parent_context));
3815         }
3816         EvalImpl(matches, non_matches, search_domain, PlanetSizeSimpleMatch(sizes, parent_context.ContextObjects()));
3817     } else {
3818         // re-evaluate contained objects for each candidate object
3819         Condition::Eval(parent_context, matches, non_matches, search_domain);
3820     }
3821 }
3822 
Description(bool negated) const3823 std::string PlanetSize::Description(bool negated/* = false*/) const {
3824     std::string values_str;
3825     for (unsigned int i = 0; i < m_sizes.size(); ++i) {
3826         values_str += m_sizes[i]->ConstantExpr() ?
3827                         UserString(boost::lexical_cast<std::string>(m_sizes[i]->Eval())) :
3828                         m_sizes[i]->Description();
3829         if (2 <= m_sizes.size() && i < m_sizes.size() - 2) {
3830             values_str += ", ";
3831         } else if (i == m_sizes.size() - 2) {
3832             values_str += m_sizes.size() < 3 ? " " : ", ";
3833             values_str += UserString("OR");
3834             values_str += " ";
3835         }
3836     }
3837     return str(FlexibleFormat((!negated)
3838         ? UserString("DESC_PLANET_SIZE")
3839         : UserString("DESC_PLANET_SIZE_NOT"))
3840         % values_str);
3841 }
3842 
Dump(unsigned short ntabs) const3843 std::string PlanetSize::Dump(unsigned short ntabs) const {
3844     std::string retval = DumpIndent(ntabs) + "Planet size = ";
3845     if (m_sizes.size() == 1) {
3846         retval += m_sizes[0]->Dump(ntabs) + "\n";
3847     } else {
3848         retval += "[ ";
3849         for (auto& size : m_sizes) {
3850             retval += size->Dump(ntabs) + " ";
3851         }
3852         retval += "]\n";
3853     }
3854     return retval;
3855 }
3856 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const3857 void PlanetSize::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
3858                                                    ObjectSet& condition_non_targets) const
3859 {
3860     AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
3861     AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
3862 }
3863 
Match(const ScriptingContext & local_context) const3864 bool PlanetSize::Match(const ScriptingContext& local_context) const {
3865     auto candidate = local_context.condition_local_candidate;
3866     if (!candidate) {
3867         ErrorLogger() << "PlanetSize::Match passed no candidate object";
3868         return false;
3869     }
3870 
3871     auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
3872     std::shared_ptr<const ::Building> building;
3873     if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
3874         planet = local_context.ContextObjects().get<Planet>(building->PlanetID());
3875 
3876     if (planet) {
3877         for (auto& size : m_sizes) {
3878             if (size->Eval(local_context) == planet->Size())
3879                 return true;
3880         }
3881     }
3882     return false;
3883 }
3884 
SetTopLevelContent(const std::string & content_name)3885 void PlanetSize::SetTopLevelContent(const std::string& content_name) {
3886     for (auto& size : m_sizes) {
3887         if (size)
3888             size->SetTopLevelContent(content_name);
3889     }
3890 }
3891 
GetCheckSum() const3892 unsigned int PlanetSize::GetCheckSum() const {
3893     unsigned int retval{0};
3894 
3895     CheckSums::CheckSumCombine(retval, "Condition::PlanetSize");
3896     CheckSums::CheckSumCombine(retval, m_sizes);
3897 
3898     TraceLogger() << "GetCheckSum(PlanetSize): retval: " << retval;
3899     return retval;
3900 }
3901 
3902 ///////////////////////////////////////////////////////////
3903 // PlanetEnvironment                                     //
3904 ///////////////////////////////////////////////////////////
PlanetEnvironment(std::vector<std::unique_ptr<ValueRef::ValueRef<::PlanetEnvironment>>> && environments,std::unique_ptr<ValueRef::ValueRef<std::string>> && species_name_ref)3905 PlanetEnvironment::PlanetEnvironment(std::vector<std::unique_ptr<ValueRef::ValueRef< ::PlanetEnvironment>>>&& environments,
3906                                      std::unique_ptr<ValueRef::ValueRef<std::string>>&& species_name_ref) :
3907     Condition(),
3908     m_environments(std::move(environments)),
3909     m_species_name(std::move(species_name_ref))
3910 {
3911     m_root_candidate_invariant =
3912         (!m_species_name || m_species_name->RootCandidateInvariant()) &&
3913         boost::algorithm::all_of(m_environments, [](auto& e){ return !e || e->RootCandidateInvariant(); });
3914     m_target_invariant =
3915         (!m_species_name || m_species_name->TargetInvariant()) &&
3916         boost::algorithm::all_of(m_environments, [](auto& e){ return !e || e->TargetInvariant(); });
3917     m_source_invariant =
3918         (!m_species_name || m_species_name->SourceInvariant()) &&
3919         boost::algorithm::all_of(m_environments, [](auto& e){ return !e || e->SourceInvariant(); });
3920 }
3921 
operator ==(const Condition & rhs) const3922 bool PlanetEnvironment::operator==(const Condition& rhs) const {
3923     if (this == &rhs)
3924         return true;
3925     if (typeid(*this) != typeid(rhs))
3926         return false;
3927 
3928     const PlanetEnvironment& rhs_ = static_cast<const PlanetEnvironment&>(rhs);
3929 
3930     CHECK_COND_VREF_MEMBER(m_species_name)
3931 
3932     if (m_environments.size() != rhs_.m_environments.size())
3933         return false;
3934     for (unsigned int i = 0; i < m_environments.size(); ++i) {
3935         CHECK_COND_VREF_MEMBER(m_environments.at(i))
3936     }
3937 
3938     return true;
3939 }
3940 
3941 namespace {
3942     struct PlanetEnvironmentSimpleMatch {
PlanetEnvironmentSimpleMatchCondition::__anone9a56faf2f11::PlanetEnvironmentSimpleMatch3943         PlanetEnvironmentSimpleMatch(const std::vector< ::PlanetEnvironment>& environments,
3944                                      const ObjectMap& objects,
3945                                      const std::string& species = "") :
3946             m_environments(environments),
3947             m_species(species),
3948             m_objects(objects)
3949         {}
3950 
operator ()Condition::__anone9a56faf2f11::PlanetEnvironmentSimpleMatch3951         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
3952             if (!candidate)
3953                 return false;
3954 
3955             // is it a planet or on a planet? TODO: factor out
3956             auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
3957             std::shared_ptr<const ::Building> building;
3958             if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
3959                 planet = m_objects.get<Planet>(building->PlanetID());
3960             if (planet) {
3961                 // is it one of the specified building types?
3962                 for (auto environment : m_environments) {
3963                     if (planet->EnvironmentForSpecies(m_species) == environment)
3964                         return true;
3965                 }
3966             }
3967 
3968             return false;
3969         }
3970 
3971         const std::vector< ::PlanetEnvironment>&    m_environments;
3972         const std::string&                          m_species;
3973         const ObjectMap&                            m_objects;
3974     };
3975 }
3976 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const3977 void PlanetEnvironment::Eval(const ScriptingContext& parent_context,
3978                              ObjectSet& matches, ObjectSet& non_matches,
3979                              SearchDomain search_domain/* = NON_MATCHES*/) const
3980 {
3981     bool simple_eval_safe = ((!m_species_name || m_species_name->LocalCandidateInvariant()) &&
3982                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
3983     if (simple_eval_safe) {
3984         // check each valueref for invariance to local candidate
3985         for (auto& environment : m_environments) {
3986             if (!environment->LocalCandidateInvariant()) {
3987                 simple_eval_safe = false;
3988                 break;
3989             }
3990         }
3991     }
3992     if (simple_eval_safe) {
3993         // evaluate types once, and use to check all candidate objects
3994         std::vector< ::PlanetEnvironment> environments;
3995         // get all types from valuerefs
3996         for (auto& environment : m_environments) {
3997             environments.push_back(environment->Eval(parent_context));
3998         }
3999         std::string species_name;
4000         if (m_species_name)
4001             species_name = m_species_name->Eval(parent_context);
4002         EvalImpl(matches, non_matches, search_domain, PlanetEnvironmentSimpleMatch(environments, parent_context.ContextObjects(), species_name));
4003     } else {
4004         // re-evaluate contained objects for each candidate object
4005         Condition::Eval(parent_context, matches, non_matches, search_domain);
4006     }
4007 }
4008 
Description(bool negated) const4009 std::string PlanetEnvironment::Description(bool negated/* = false*/) const {
4010     std::string values_str;
4011     for (unsigned int i = 0; i < m_environments.size(); ++i) {
4012         values_str += m_environments[i]->ConstantExpr() ?
4013                         UserString(boost::lexical_cast<std::string>(m_environments[i]->Eval())) :
4014                         m_environments[i]->Description();
4015         if (2 <= m_environments.size() && i < m_environments.size() - 2) {
4016             values_str += ", ";
4017         } else if (i == m_environments.size() - 2) {
4018             values_str += m_environments.size() < 3 ? " " : ", ";
4019             values_str += UserString("OR");
4020             values_str += " ";
4021         }
4022     }
4023     std::string species_str;
4024     if (m_species_name) {
4025         species_str = m_species_name->Description();
4026         if (m_species_name->ConstantExpr() && UserStringExists(species_str))
4027             species_str = UserString(species_str);
4028     }
4029     if (species_str.empty())
4030         species_str = UserString("DESC_PLANET_ENVIRONMENT_CUR_SPECIES");
4031     return str(FlexibleFormat((!negated)
4032         ? UserString("DESC_PLANET_ENVIRONMENT")
4033         : UserString("DESC_PLANET_ENVIRONMENT_NOT"))
4034         % values_str
4035         % species_str);
4036 }
4037 
Dump(unsigned short ntabs) const4038 std::string PlanetEnvironment::Dump(unsigned short ntabs) const {
4039     std::string retval = DumpIndent(ntabs) + "Planet environment = ";
4040     if (m_environments.size() == 1) {
4041         retval += m_environments[0]->Dump(ntabs);
4042     } else {
4043         retval += "[ ";
4044         for (auto& environment : m_environments) {
4045             retval += environment->Dump(ntabs) + " ";
4046         }
4047         retval += "]";
4048     }
4049     if (m_species_name)
4050         retval += " species = " + m_species_name->Dump(ntabs);
4051     retval += "\n";
4052     return retval;
4053 }
4054 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const4055 void PlanetEnvironment::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
4056                                                           ObjectSet& condition_non_targets) const
4057 {
4058     AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
4059     AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
4060 }
4061 
Match(const ScriptingContext & local_context) const4062 bool PlanetEnvironment::Match(const ScriptingContext& local_context) const {
4063     auto candidate = local_context.condition_local_candidate;
4064     if (!candidate) {
4065         ErrorLogger() << "PlanetEnvironment::Match passed no candidate object";
4066         return false;
4067     }
4068 
4069     // is it a planet or on a planet? TODO: factor out
4070     auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
4071     std::shared_ptr<const ::Building> building;
4072     if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
4073         planet = local_context.ContextObjects().get<Planet>(building->PlanetID());
4074     if (!planet)
4075         return false;
4076 
4077     std::string species_name;
4078     if (m_species_name)
4079         species_name = m_species_name->Eval(local_context);
4080 
4081     auto env_for_planets_species = planet->EnvironmentForSpecies(species_name);
4082     for (auto& environment : m_environments) {
4083         if (environment->Eval(local_context) == env_for_planets_species)
4084             return true;
4085     }
4086     return false;
4087 }
4088 
SetTopLevelContent(const std::string & content_name)4089 void PlanetEnvironment::SetTopLevelContent(const std::string& content_name) {
4090     if (m_species_name)
4091         m_species_name->SetTopLevelContent(content_name);
4092     for (auto& environment : m_environments) {
4093         if (environment)
4094             environment->SetTopLevelContent(content_name);
4095     }
4096 }
4097 
GetCheckSum() const4098 unsigned int PlanetEnvironment::GetCheckSum() const {
4099     unsigned int retval{0};
4100 
4101     CheckSums::CheckSumCombine(retval, "Condition::PlanetEnvironment");
4102     CheckSums::CheckSumCombine(retval, m_environments);
4103     CheckSums::CheckSumCombine(retval, m_species_name);
4104 
4105     TraceLogger() << "GetCheckSum(PlanetEnvironment): retval: " << retval;
4106     return retval;
4107 }
4108 
4109 ///////////////////////////////////////////////////////////
4110 // Species                                               //
4111 ///////////////////////////////////////////////////////////
Species(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>> && names)4112 Species::Species(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>>&& names) :
4113     Condition(),
4114     m_names(std::move(names))
4115 {
4116     m_root_candidate_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->RootCandidateInvariant(); });
4117     m_target_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->TargetInvariant(); });
4118     m_source_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->SourceInvariant(); });
4119 }
4120 
Species()4121 Species::Species() :
4122     Species(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>>{})
4123 {}
4124 
operator ==(const Condition & rhs) const4125 bool Species::operator==(const Condition& rhs) const {
4126     if (this == &rhs)
4127         return true;
4128     if (typeid(*this) != typeid(rhs))
4129         return false;
4130 
4131     const Species& rhs_ = static_cast<const Species&>(rhs);
4132 
4133     if (m_names.size() != rhs_.m_names.size())
4134         return false;
4135     for (unsigned int i = 0; i < m_names.size(); ++i) {
4136         CHECK_COND_VREF_MEMBER(m_names.at(i))
4137     }
4138 
4139     return true;
4140 }
4141 
4142 namespace {
4143     struct SpeciesSimpleMatch {
SpeciesSimpleMatchCondition::__anone9a56faf3311::SpeciesSimpleMatch4144         SpeciesSimpleMatch(const std::vector<std::string>& names, const ObjectMap& objects) :
4145             m_names(names),
4146             m_objects(objects)
4147         {}
4148 
operator ()Condition::__anone9a56faf3311::SpeciesSimpleMatch4149         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
4150             if (!candidate)
4151                 return false;
4152 
4153             // is it a population centre?
4154             if (auto pop = std::dynamic_pointer_cast<const ::PopCenter>(candidate)) {
4155                 const std::string& species_name = pop->SpeciesName();
4156                 // if the popcenter has a species and that species is one of those specified...
4157                 return !species_name.empty() && (m_names.empty() || std::count(m_names.begin(), m_names.end(), species_name));
4158             }
4159             // is it a ship?
4160             if (auto ship = std::dynamic_pointer_cast<const Ship>(candidate)) {
4161                 // if the ship has a species and that species is one of those specified...
4162                 const std::string& species_name = ship->SpeciesName();
4163                 return !species_name.empty() && (m_names.empty() || std::count(m_names.begin(), m_names.end(), species_name));
4164             }
4165             // is it a building on a planet?
4166             if (auto building = std::dynamic_pointer_cast<const ::Building>(candidate)) {
4167                 auto planet = m_objects.get<Planet>(building->PlanetID());
4168                 const std::string& species_name = planet->SpeciesName();
4169                 // if the planet (which IS a popcenter) has a species and that species is one of those specified...
4170                 return !species_name.empty() && (m_names.empty() || std::count(m_names.begin(), m_names.end(), species_name));
4171             }
4172 
4173             return false;
4174         }
4175 
4176         const std::vector<std::string>& m_names;
4177         const ObjectMap& m_objects;
4178     };
4179 }
4180 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const4181 void Species::Eval(const ScriptingContext& parent_context,
4182                    ObjectSet& matches, ObjectSet& non_matches,
4183                    SearchDomain search_domain/* = NON_MATCHES*/) const
4184 {
4185     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
4186     if (simple_eval_safe) {
4187         // check each valueref for invariance to local candidate
4188         for (auto& name : m_names) {
4189             if (!name->LocalCandidateInvariant()) {
4190                 simple_eval_safe = false;
4191                 break;
4192             }
4193         }
4194     }
4195     if (simple_eval_safe) {
4196         // evaluate names once, and use to check all candidate objects
4197         std::vector<std::string> names;
4198         // get all names from valuerefs
4199         for (auto& name : m_names) {
4200             names.push_back(name->Eval(parent_context));
4201         }
4202         EvalImpl(matches, non_matches, search_domain, SpeciesSimpleMatch(names, parent_context.ContextObjects()));
4203     } else {
4204         // re-evaluate allowed building types range for each candidate object
4205         Condition::Eval(parent_context, matches, non_matches, search_domain);
4206     }
4207 }
4208 
Description(bool negated) const4209 std::string Species::Description(bool negated/* = false*/) const {
4210     std::string values_str;
4211     if (m_names.empty())
4212         values_str = "(" + UserString("CONDITION_ANY") +")";
4213     for (unsigned int i = 0; i < m_names.size(); ++i) {
4214         values_str += m_names[i]->ConstantExpr() ?
4215                         UserString(m_names[i]->Eval()) :
4216                         m_names[i]->Description();
4217         if (2 <= m_names.size() && i < m_names.size() - 2) {
4218             values_str += ", ";
4219         } else if (i == m_names.size() - 2) {
4220             values_str += m_names.size() < 3 ? " " : ", ";
4221             values_str += UserString("OR");
4222             values_str += " ";
4223         }
4224     }
4225     return str(FlexibleFormat((!negated)
4226         ? UserString("DESC_SPECIES")
4227         : UserString("DESC_SPECIES_NOT"))
4228         % values_str);
4229 }
4230 
Dump(unsigned short ntabs) const4231 std::string Species::Dump(unsigned short ntabs) const {
4232     std::string retval = DumpIndent(ntabs) + "Species";
4233     if (m_names.empty()) {
4234         // do nothing else
4235     } else if (m_names.size() == 1) {
4236         retval += " name = " + m_names[0]->Dump(ntabs) + "\n";
4237     } else {
4238         retval += " name = [ ";
4239         for (auto& name : m_names) {
4240             retval += name->Dump(ntabs) + " ";
4241         }
4242         retval += "]\n";
4243     }
4244     return retval;
4245 }
4246 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const4247 void Species::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
4248                                                 ObjectSet& condition_non_targets) const
4249 {
4250     AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
4251     AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
4252     AddShipSet(parent_context.ContextObjects(), condition_non_targets);
4253 }
4254 
Match(const ScriptingContext & local_context) const4255 bool Species::Match(const ScriptingContext& local_context) const {
4256     auto candidate = local_context.condition_local_candidate;
4257     if (!candidate) {
4258         ErrorLogger() << "Species::Match passed no candidate object";
4259         return false;
4260     }
4261 
4262     // is it a planet or a building on a planet? TODO: factor out
4263     auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
4264     std::shared_ptr<const ::Building> building;
4265     if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
4266         planet = local_context.ContextObjects().get<Planet>(building->PlanetID());
4267     if (planet) {
4268         if (m_names.empty()) {
4269             return !planet->SpeciesName().empty();  // match any species name
4270         } else {
4271             // match only specified species names
4272             for (auto& name : m_names) {
4273                 if (name->Eval(local_context) == planet->SpeciesName())
4274                     return true;
4275             }
4276         }
4277     }
4278     // is it a ship?
4279     auto ship = std::dynamic_pointer_cast<const Ship>(candidate);
4280     if (ship) {
4281         if (m_names.empty()) {
4282             return !ship->SpeciesName().empty();    // match any species name
4283         } else {
4284             // match only specified species names
4285             for (auto& name : m_names) {
4286                 if (name->Eval(local_context) == ship->SpeciesName())
4287                     return true;
4288             }
4289         }
4290     }
4291     return false;
4292 }
4293 
SetTopLevelContent(const std::string & content_name)4294 void Species::SetTopLevelContent(const std::string& content_name) {
4295     for (auto& name : m_names) {
4296         if (name)
4297             name->SetTopLevelContent(content_name);
4298     }
4299 }
4300 
GetCheckSum() const4301 unsigned int Species::GetCheckSum() const {
4302     unsigned int retval{0};
4303 
4304     CheckSums::CheckSumCombine(retval, "Condition::Species");
4305     CheckSums::CheckSumCombine(retval, m_names);
4306 
4307     TraceLogger() << "GetCheckSum(Species): retval: " << retval;
4308     return retval;
4309 }
4310 
4311 ///////////////////////////////////////////////////////////
4312 // Enqueued                                              //
4313 ///////////////////////////////////////////////////////////
Enqueued(std::unique_ptr<ValueRef::ValueRef<int>> && design_id,std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,std::unique_ptr<ValueRef::ValueRef<int>> && low,std::unique_ptr<ValueRef::ValueRef<int>> && high)4314 Enqueued::Enqueued(std::unique_ptr<ValueRef::ValueRef<int>>&& design_id,
4315                    std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
4316                    std::unique_ptr<ValueRef::ValueRef<int>>&& low,
4317                    std::unique_ptr<ValueRef::ValueRef<int>>&& high) :
4318     Condition(),
4319     m_build_type(BT_SHIP),
4320     m_design_id(std::move(design_id)),
4321     m_empire_id(std::move(empire_id)),
4322     m_low(std::move(low)),
4323     m_high(std::move(high))
4324 {
4325     auto operands = {m_design_id.get(), m_empire_id.get(), m_low.get(), m_high.get()};
4326     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
4327     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
4328     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
4329 }
4330 
Enqueued()4331 Enqueued::Enqueued() :
4332     Enqueued(BT_NOT_BUILDING, nullptr, nullptr, nullptr)
4333 {}
4334 
Enqueued(BuildType build_type,std::unique_ptr<ValueRef::ValueRef<std::string>> && name,std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,std::unique_ptr<ValueRef::ValueRef<int>> && low,std::unique_ptr<ValueRef::ValueRef<int>> && high)4335 Enqueued::Enqueued(BuildType build_type,
4336                    std::unique_ptr<ValueRef::ValueRef<std::string>>&& name,
4337                    std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
4338                    std::unique_ptr<ValueRef::ValueRef<int>>&& low,
4339                    std::unique_ptr<ValueRef::ValueRef<int>>&& high) :
4340     Condition(),
4341     m_build_type(build_type),
4342     m_name(std::move(name)),
4343     m_empire_id(std::move(empire_id)),
4344     m_low(std::move(low)),
4345     m_high(std::move(high))
4346 {
4347     auto operands = {m_empire_id.get(), m_low.get(), m_high.get()};
4348     m_root_candidate_invariant =
4349         (!m_name || m_name->RootCandidateInvariant()) &&
4350         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
4351     m_target_invariant =
4352         (!m_name || m_name->TargetInvariant()) &&
4353         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
4354     m_source_invariant =
4355         (!m_name || m_name->SourceInvariant()) &&
4356         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
4357 }
4358 
operator ==(const Condition & rhs) const4359 bool Enqueued::operator==(const Condition& rhs) const {
4360     if (this == &rhs)
4361         return true;
4362     if (typeid(*this) != typeid(rhs))
4363         return false;
4364 
4365     const Enqueued& rhs_ = static_cast<const Enqueued&>(rhs);
4366 
4367     if (m_build_type != rhs_.m_build_type)
4368         return false;
4369 
4370     CHECK_COND_VREF_MEMBER(m_name)
4371     CHECK_COND_VREF_MEMBER(m_design_id)
4372     CHECK_COND_VREF_MEMBER(m_empire_id)
4373     CHECK_COND_VREF_MEMBER(m_low)
4374     CHECK_COND_VREF_MEMBER(m_high)
4375 
4376     return true;
4377 }
4378 
4379 namespace {
NumberOnQueue(const ProductionQueue & queue,BuildType build_type,const int location_id,const std::string & name="",int design_id=INVALID_DESIGN_ID)4380     int NumberOnQueue(const ProductionQueue& queue, BuildType build_type, const int location_id,
4381                       const std::string& name = "", int design_id = INVALID_DESIGN_ID)
4382     {
4383         int retval = 0;
4384         for (const auto& element : queue) {
4385             if (!(build_type == INVALID_BUILD_TYPE || build_type == element.item.build_type))
4386                 continue;
4387             if (location_id != element.location)
4388                 continue;
4389             if (build_type == BT_BUILDING) {
4390                 // if looking for buildings, accept specifically named building
4391                 // or any building if no name specified
4392                 if (!name.empty() && element.item.name != name)
4393                     continue;
4394             } else if (build_type == BT_SHIP) {
4395                 if (design_id != INVALID_DESIGN_ID) {
4396                     // if looking for ships, accept design by id number...
4397                     if (design_id != element.item.design_id)
4398                         continue;
4399                 } else if (!name.empty()) {
4400                     // ... or accept design by predefined name
4401                     const ShipDesign* design = GetShipDesign(element.item.design_id);
4402                     if (!design || name != design->Name(false))
4403                         continue;
4404                 }
4405             } // else: looking for any production item
4406 
4407             retval += element.blocksize;
4408         }
4409         return retval;
4410     }
4411 
4412     struct EnqueuedSimpleMatch {
EnqueuedSimpleMatchCondition::__anone9a56faf3a11::EnqueuedSimpleMatch4413         EnqueuedSimpleMatch(BuildType build_type, const std::string& name, int design_id,
4414                             int empire_id, int low, int high) :
4415             m_build_type(build_type),
4416             m_name(name),
4417             m_design_id(design_id),
4418             m_empire_id(empire_id),
4419             m_low(low),
4420             m_high(high)
4421         {}
operator ()Condition::__anone9a56faf3a11::EnqueuedSimpleMatch4422         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
4423             if (!candidate)
4424                 return false;
4425 
4426             int count = 0;
4427 
4428             if (m_empire_id == ALL_EMPIRES) {
4429                 for (auto& item : Empires()) {
4430                     const Empire* empire = item.second;
4431                     count += NumberOnQueue(empire->GetProductionQueue(), m_build_type,
4432                                            candidate->ID(), m_name, m_design_id);
4433                 }
4434 
4435             } else {
4436                 const Empire* empire = GetEmpire(m_empire_id);
4437                 if (!empire) return false;
4438                 count = NumberOnQueue(empire->GetProductionQueue(), m_build_type,
4439                                       candidate->ID(), m_name, m_design_id);
4440             }
4441 
4442             return (m_low <= count && count <= m_high);
4443         }
4444 
4445         BuildType   m_build_type;
4446         std::string m_name;
4447         int         m_design_id;
4448         int         m_empire_id;
4449         int         m_low;
4450         int         m_high;
4451     };
4452 }
4453 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const4454 void Enqueued::Eval(const ScriptingContext& parent_context,
4455                     ObjectSet& matches, ObjectSet& non_matches,
4456                     SearchDomain search_domain/* = NON_MATCHES*/) const
4457 {
4458     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
4459     if (simple_eval_safe) {
4460         // check each valueref for invariance to local candidate
4461         if ((m_name &&      !m_name->LocalCandidateInvariant()) ||
4462             (m_design_id && !m_design_id->LocalCandidateInvariant()) ||
4463             (m_empire_id && !m_empire_id->LocalCandidateInvariant()) ||
4464             (m_low &&       !m_low->LocalCandidateInvariant()) ||
4465             (m_high &&      !m_high->LocalCandidateInvariant()))
4466         { simple_eval_safe = false; }
4467     }
4468 
4469     if (simple_eval_safe) {
4470         // evaluate valuerefs once, and use to check all candidate objects
4471         std::string name =  (m_name ?       m_name->Eval(parent_context) :      "");
4472         int design_id =     (m_design_id ?  m_design_id->Eval(parent_context) : INVALID_DESIGN_ID);
4473         int empire_id =     (m_empire_id ?  m_empire_id->Eval(parent_context) : ALL_EMPIRES);
4474         int low =           (m_low ?        m_low->Eval(parent_context) :       0);
4475         int high =          (m_high ?       m_high->Eval(parent_context) :      INT_MAX);
4476         // special case: if neither low nor high is specified, default to a
4477         // minimum of 1, so that just matching "Enqueued (type) (name/id)" will
4478         // match places where at least one of the specified item is enqueued.
4479         // if a max or other minimum are specified, then default to 0 low, so
4480         // that just specifying a max will include anything below that max,
4481         // including 0.
4482         if (!m_low && !m_high)
4483             low = 1;
4484 
4485         // need to test each candidate separately using EvalImpl and EnqueuedSimpleMatch
4486         // because the test checks that something is enqueued at the candidate location
4487         EvalImpl(matches, non_matches, search_domain, EnqueuedSimpleMatch(m_build_type, name, design_id,
4488                                                                           empire_id, low, high));
4489     } else {
4490         // re-evaluate allowed building types range for each candidate object
4491         Condition::Eval(parent_context, matches, non_matches, search_domain);
4492     }
4493 }
4494 
Description(bool negated) const4495 std::string Enqueued::Description(bool negated/* = false*/) const {
4496     std::string empire_str;
4497     if (m_empire_id) {
4498         int empire_id = ALL_EMPIRES;
4499         if (m_empire_id->ConstantExpr())
4500             empire_id = m_empire_id->Eval();
4501         if (const Empire* empire = GetEmpire(empire_id))
4502             empire_str = empire->Name();
4503         else
4504             empire_str = m_empire_id->Description();
4505     }
4506     std::string low_str = "1";
4507     if (m_low) {
4508         low_str = m_low->ConstantExpr() ?
4509                     std::to_string(m_low->Eval()) :
4510                     m_low->Description();
4511     }
4512     std::string high_str = std::to_string(INT_MAX);
4513     if (m_high) {
4514         high_str = m_high->ConstantExpr() ?
4515                     std::to_string(m_high->Eval()) :
4516                     m_high->Description();
4517     }
4518     std::string what_str;
4519     if (m_name) {
4520         what_str = m_name->Description();
4521         if (m_name->ConstantExpr() && UserStringExists(what_str))
4522             what_str = UserString(what_str);
4523     } else if (m_design_id) {
4524         what_str = m_design_id->ConstantExpr() ?
4525                     std::to_string(m_design_id->Eval()) :
4526                     m_design_id->Description();
4527     }
4528     std::string description_str;
4529     switch (m_build_type) {
4530     case BT_BUILDING:   description_str = (!negated)
4531                             ? UserString("DESC_ENQUEUED_BUILDING")
4532                             : UserString("DESC_ENQUEUED_BUILDING_NOT");
4533     break;
4534     case BT_SHIP:       description_str = (!negated)
4535                             ? UserString("DESC_ENQUEUED_DESIGN")
4536                             : UserString("DESC_ENQUEUED_DESIGN_NOT");
4537     break;
4538     default:            description_str = (!negated)
4539                             ? UserString("DESC_ENQUEUED")
4540                             : UserString("DESC_ENQUEUED_NOT");
4541     break;
4542     }
4543     return str(FlexibleFormat(description_str)
4544                % empire_str
4545                % low_str
4546                % high_str
4547                % what_str);
4548 }
4549 
Dump(unsigned short ntabs) const4550 std::string Enqueued::Dump(unsigned short ntabs) const {
4551     std::string retval = DumpIndent(ntabs) + "Enqueued";
4552 
4553     if (m_build_type == BT_BUILDING) {
4554         retval += " type = Building";
4555         if (m_name)
4556             retval += " name = " + m_name->Dump(ntabs);
4557     } else if (m_build_type == BT_SHIP) {
4558         retval += " type = Ship";
4559         if (m_name)
4560             retval += " design = " + m_name->Dump(ntabs);
4561         else if (m_design_id)
4562             retval += " design = " + m_design_id->Dump(ntabs);
4563     }
4564     if (m_empire_id)
4565         retval += " empire = " + m_empire_id->Dump(ntabs);
4566     if (m_low)
4567         retval += " low = " + m_low->Dump(ntabs);
4568     if (m_high)
4569         retval += " high = " + m_high->Dump(ntabs);
4570     retval += "\n";
4571     return retval;
4572 }
4573 
Match(const ScriptingContext & local_context) const4574 bool Enqueued::Match(const ScriptingContext& local_context) const {
4575     auto candidate = local_context.condition_local_candidate;
4576     if (!candidate) {
4577         ErrorLogger() << "Enqueued::Match passed no candidate object";
4578         return false;
4579     }
4580     std::string name =  (m_name ?       m_name->Eval(local_context) :       "");
4581     int empire_id =     (m_empire_id ?  m_empire_id->Eval(local_context) :  ALL_EMPIRES);
4582     int design_id =     (m_design_id ?  m_design_id->Eval(local_context) :  INVALID_DESIGN_ID);
4583     int low =           (m_low ?        m_low->Eval(local_context) :        0);
4584     int high =          (m_high ?       m_high->Eval(local_context) :       INT_MAX);
4585     return EnqueuedSimpleMatch(m_build_type, name, design_id, empire_id, low, high)(candidate);
4586 }
4587 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const4588 void Enqueued::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
4589                                                  ObjectSet& condition_non_targets) const
4590 {
4591     AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
4592 }
4593 
SetTopLevelContent(const std::string & content_name)4594 void Enqueued::SetTopLevelContent(const std::string& content_name) {
4595     if (m_name)
4596         m_name->SetTopLevelContent(content_name);
4597     if (m_design_id)
4598         m_design_id->SetTopLevelContent(content_name);
4599     if (m_empire_id)
4600         m_empire_id->SetTopLevelContent(content_name);
4601     if (m_low)
4602         m_low->SetTopLevelContent(content_name);
4603     if (m_high)
4604         m_high->SetTopLevelContent(content_name);
4605 }
4606 
GetCheckSum() const4607 unsigned int Enqueued::GetCheckSum() const {
4608     unsigned int retval{0};
4609 
4610     CheckSums::CheckSumCombine(retval, "Condition::Enqueued");
4611     CheckSums::CheckSumCombine(retval, m_name);
4612     CheckSums::CheckSumCombine(retval, m_design_id);
4613     CheckSums::CheckSumCombine(retval, m_empire_id);
4614     CheckSums::CheckSumCombine(retval, m_low);
4615     CheckSums::CheckSumCombine(retval, m_high);
4616 
4617     TraceLogger() << "GetCheckSum(Enqueued): retval: " << retval;
4618     return retval;
4619 }
4620 
4621 ///////////////////////////////////////////////////////////
4622 // FocusType                                             //
4623 ///////////////////////////////////////////////////////////
FocusType(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>> && names)4624 FocusType::FocusType(std::vector<std::unique_ptr<ValueRef::ValueRef<std::string>>>&& names) :
4625     Condition(),
4626     m_names(std::move(names))
4627 {
4628     m_root_candidate_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->RootCandidateInvariant(); });
4629     m_target_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->TargetInvariant(); });
4630     m_source_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->SourceInvariant(); });
4631 }
4632 
operator ==(const Condition & rhs) const4633 bool FocusType::operator==(const Condition& rhs) const {
4634     if (this == &rhs)
4635         return true;
4636     if (typeid(*this) != typeid(rhs))
4637         return false;
4638 
4639     const FocusType& rhs_ = static_cast<const FocusType&>(rhs);
4640 
4641     if (m_names.size() != rhs_.m_names.size())
4642         return false;
4643     for (unsigned int i = 0; i < m_names.size(); ++i) {
4644         CHECK_COND_VREF_MEMBER(m_names.at(i))
4645     }
4646 
4647     return true;
4648 }
4649 
4650 namespace {
4651     struct FocusTypeSimpleMatch {
FocusTypeSimpleMatchCondition::__anone9a56faf3e11::FocusTypeSimpleMatch4652         FocusTypeSimpleMatch(const std::vector<std::string>& names, const ObjectMap& objects) :
4653             m_names(names),
4654             m_objects(objects)
4655         {}
4656 
operator ()Condition::__anone9a56faf3e11::FocusTypeSimpleMatch4657         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
4658             if (!candidate)
4659                 return false;
4660 
4661             // is it a ResourceCenter or a Building on a Planet (that is a ResourceCenter)
4662             auto res_center = std::dynamic_pointer_cast<const ResourceCenter>(candidate);
4663             std::shared_ptr<const ::Building> building;
4664             if (!res_center && (building = std::dynamic_pointer_cast<const ::Building>(candidate))) {
4665                 if (auto planet = m_objects.get<Planet>(building->PlanetID()))
4666                     res_center = std::dynamic_pointer_cast<const ResourceCenter>(planet);
4667             }
4668             if (res_center) {
4669                 return !res_center->Focus().empty() &&
4670                     std::count(m_names.begin(), m_names.end(), res_center->Focus());
4671             }
4672 
4673             return false;
4674         }
4675 
4676         const std::vector<std::string>& m_names;
4677         const ObjectMap& m_objects;
4678     };
4679 }
4680 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const4681 void FocusType::Eval(const ScriptingContext& parent_context,
4682                      ObjectSet& matches, ObjectSet& non_matches,
4683                      SearchDomain search_domain/* = NON_MATCHES*/) const
4684 {
4685     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
4686     if (simple_eval_safe) {
4687         // check each valueref for invariance to local candidate
4688         for (auto& name : m_names) {
4689             if (!name->LocalCandidateInvariant()) {
4690                 simple_eval_safe = false;
4691                 break;
4692             }
4693         }
4694     }
4695     if (simple_eval_safe) {
4696         // evaluate names once, and use to check all candidate objects
4697         std::vector<std::string> names;
4698         // get all names from valuerefs
4699         for (auto& name : m_names) {
4700             names.push_back(name->Eval(parent_context));
4701         }
4702         EvalImpl(matches, non_matches, search_domain, FocusTypeSimpleMatch(names, parent_context.ContextObjects()));
4703     } else {
4704         // re-evaluate allowed building types range for each candidate object
4705         Condition::Eval(parent_context, matches, non_matches, search_domain);
4706     }
4707 }
4708 
Description(bool negated) const4709 std::string FocusType::Description(bool negated/* = false*/) const {
4710     std::string values_str;
4711     for (unsigned int i = 0; i < m_names.size(); ++i) {
4712         values_str += m_names[i]->ConstantExpr() ?
4713             UserString(m_names[i]->Eval()) :
4714             m_names[i]->Description();
4715         if (2 <= m_names.size() && i < m_names.size() - 2) {
4716             values_str += ", ";
4717         } else if (i == m_names.size() - 2) {
4718             values_str += m_names.size() < 3 ? " " : ", ";
4719             values_str += UserString("OR");
4720             values_str += " ";
4721         }
4722     }
4723     return str(FlexibleFormat((!negated)
4724         ? UserString("DESC_FOCUS_TYPE")
4725         : UserString("DESC_FOCUS_TYPE_NOT"))
4726         % values_str);
4727 }
4728 
Dump(unsigned short ntabs) const4729 std::string FocusType::Dump(unsigned short ntabs) const {
4730     std::string retval = DumpIndent(ntabs) + "Focus name = ";
4731     if (m_names.size() == 1) {
4732         retval += m_names[0]->Dump(ntabs) + "\n";
4733     } else {
4734         retval += "[ ";
4735         for (auto& name : m_names) {
4736             retval += name->Dump(ntabs) + " ";
4737         }
4738         retval += "]\n";
4739     }
4740     return retval;
4741 }
4742 
Match(const ScriptingContext & local_context) const4743 bool FocusType::Match(const ScriptingContext& local_context) const {
4744     auto candidate = local_context.condition_local_candidate;
4745     if (!candidate) {
4746         ErrorLogger() << "FocusType::Match passed no candidate object";
4747         return false;
4748     }
4749 
4750     // is it a ResourceCenter or a Building on a Planet (that is a ResourceCenter)
4751     auto res_center = std::dynamic_pointer_cast<const ResourceCenter>(candidate);
4752     std::shared_ptr<const ::Building> building;
4753     if (!res_center && (building = std::dynamic_pointer_cast<const ::Building>(candidate))) {
4754         if (auto planet = local_context.ContextObjects().get<Planet>(building->PlanetID()))
4755             res_center = std::dynamic_pointer_cast<const ResourceCenter>(planet);
4756     }
4757     if (res_center) {
4758         for (auto& name : m_names) {
4759             if (name->Eval(local_context) == res_center->Focus())
4760                 return true;
4761         }
4762     }
4763     return false;
4764 }
4765 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const4766 void FocusType::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
4767                                                   ObjectSet& condition_non_targets) const
4768 {
4769     AddPlanetSet(parent_context.ContextObjects(), condition_non_targets);
4770     AddBuildingSet(parent_context.ContextObjects(), condition_non_targets);
4771 }
4772 
SetTopLevelContent(const std::string & content_name)4773 void FocusType::SetTopLevelContent(const std::string& content_name) {
4774     for (auto& name : m_names) {
4775         if (name)
4776             name->SetTopLevelContent(content_name);
4777     }
4778 }
4779 
GetCheckSum() const4780 unsigned int FocusType::GetCheckSum() const {
4781     unsigned int retval{0};
4782 
4783     CheckSums::CheckSumCombine(retval, "Condition::FocusType");
4784     CheckSums::CheckSumCombine(retval, m_names);
4785 
4786     TraceLogger() << "GetCheckSum(FocusType): retval: " << retval;
4787     return retval;
4788 }
4789 
4790 ///////////////////////////////////////////////////////////
4791 // StarType                                              //
4792 ///////////////////////////////////////////////////////////
StarType(std::vector<std::unique_ptr<ValueRef::ValueRef<::StarType>>> && types)4793 StarType::StarType(std::vector<std::unique_ptr<ValueRef::ValueRef< ::StarType>>>&& types) :
4794     Condition(),
4795     m_types(std::move(types))
4796 {
4797     m_root_candidate_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->RootCandidateInvariant(); });
4798     m_target_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->TargetInvariant(); });
4799     m_source_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->SourceInvariant(); });
4800 }
4801 
operator ==(const Condition & rhs) const4802 bool StarType::operator==(const Condition& rhs) const {
4803     if (this == &rhs)
4804         return true;
4805     if (typeid(*this) != typeid(rhs))
4806         return false;
4807 
4808     const StarType& rhs_ = static_cast<const StarType&>(rhs);
4809 
4810     if (m_types.size() != rhs_.m_types.size())
4811         return false;
4812     for (unsigned int i = 0; i < m_types.size(); ++i) {
4813         CHECK_COND_VREF_MEMBER(m_types.at(i))
4814     }
4815 
4816     return true;
4817 }
4818 
4819 namespace {
4820     struct StarTypeSimpleMatch {
StarTypeSimpleMatchCondition::__anone9a56faf4211::StarTypeSimpleMatch4821         StarTypeSimpleMatch(const std::vector< ::StarType>& types, const ObjectMap& objects) :
4822             m_types(types),
4823             m_objects(objects)
4824         {}
4825 
operator ()Condition::__anone9a56faf4211::StarTypeSimpleMatch4826         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
4827             if (!candidate)
4828                 return false;
4829 
4830             std::shared_ptr<const System> system = m_objects.get<System>(candidate->SystemID());
4831             if (system || (system = std::dynamic_pointer_cast<const System>(candidate)))
4832                 return !m_types.empty() && std::count(m_types.begin(), m_types.end(), system->GetStarType());
4833 
4834             return false;
4835         }
4836 
4837         const std::vector< ::StarType>& m_types;
4838         const ObjectMap& m_objects;
4839     };
4840 }
4841 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const4842 void StarType::Eval(const ScriptingContext& parent_context,
4843                     ObjectSet& matches, ObjectSet& non_matches,
4844                     SearchDomain search_domain/* = NON_MATCHES*/) const
4845 {
4846     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
4847     if (simple_eval_safe) {
4848         // check each valueref for invariance to local candidate
4849         for (auto& type : m_types) {
4850             if (!type->LocalCandidateInvariant()) {
4851                 simple_eval_safe = false;
4852                 break;
4853             }
4854         }
4855     }
4856     if (simple_eval_safe) {
4857         // evaluate types once, and use to check all candidate objects
4858         std::vector< ::StarType> types;
4859         // get all types from valuerefs
4860         for (auto& type : m_types) {
4861             types.push_back(type->Eval(parent_context));
4862         }
4863         EvalImpl(matches, non_matches, search_domain, StarTypeSimpleMatch(types, parent_context.ContextObjects()));
4864     } else {
4865         // re-evaluate contained objects for each candidate object
4866         Condition::Eval(parent_context, matches, non_matches, search_domain);
4867     }
4868 }
4869 
Description(bool negated) const4870 std::string StarType::Description(bool negated/* = false*/) const {
4871     std::string values_str;
4872     for (unsigned int i = 0; i < m_types.size(); ++i) {
4873         values_str += m_types[i]->ConstantExpr() ?
4874                         UserString(boost::lexical_cast<std::string>(m_types[i]->Eval())) :
4875                         m_types[i]->Description();
4876         if (2 <= m_types.size() && i < m_types.size() - 2) {
4877             values_str += ", ";
4878         } else if (i == m_types.size() - 2) {
4879             values_str += m_types.size() < 3 ? " " : ", ";
4880             values_str += UserString("OR");
4881             values_str += " ";
4882         }
4883     }
4884     return str(FlexibleFormat((!negated)
4885         ? UserString("DESC_STAR_TYPE")
4886         : UserString("DESC_STAR_TYPE_NOT"))
4887         % values_str);
4888 }
4889 
Dump(unsigned short ntabs) const4890 std::string StarType::Dump(unsigned short ntabs) const {
4891     std::string retval = DumpIndent(ntabs) + "Star type = ";
4892     if (m_types.size() == 1) {
4893         retval += m_types[0]->Dump(ntabs) + "\n";
4894     } else {
4895         retval += "[ ";
4896         for (auto& type : m_types) {
4897             retval += type->Dump(ntabs) + " ";
4898         }
4899         retval += "]\n";
4900     }
4901     return retval;
4902 }
4903 
Match(const ScriptingContext & local_context) const4904 bool StarType::Match(const ScriptingContext& local_context) const {
4905     auto candidate = local_context.condition_local_candidate;
4906     if (!candidate) {
4907         ErrorLogger() << "StarType::Match passed no candidate object";
4908         return false;
4909     }
4910 
4911     std::shared_ptr<const System> system = local_context.ContextObjects().get<System>(candidate->SystemID());
4912     if (system || (system = std::dynamic_pointer_cast<const System>(candidate))) {
4913         for (auto& type : m_types) {
4914             if (type->Eval(local_context) == system->GetStarType())
4915                 return true;
4916         }
4917     }
4918     return false;
4919 }
4920 
SetTopLevelContent(const std::string & content_name)4921 void StarType::SetTopLevelContent(const std::string& content_name) {
4922     for (auto& type : m_types) {
4923         if (type)
4924             (type)->SetTopLevelContent(content_name);
4925     }
4926 }
4927 
GetCheckSum() const4928 unsigned int StarType::GetCheckSum() const {
4929     unsigned int retval{0};
4930 
4931     CheckSums::CheckSumCombine(retval, "Condition::StarType");
4932     CheckSums::CheckSumCombine(retval, m_types);
4933 
4934     TraceLogger() << "GetCheckSum(StarType): retval: " << retval;
4935     return retval;
4936 }
4937 
4938 ///////////////////////////////////////////////////////////
4939 // DesignHasHull                                         //
4940 ///////////////////////////////////////////////////////////
DesignHasHull(std::unique_ptr<ValueRef::ValueRef<std::string>> && name)4941 DesignHasHull::DesignHasHull(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
4942     Condition(),
4943     m_name(std::move(name))
4944 {
4945     m_root_candidate_invariant = !m_name || m_name->RootCandidateInvariant();
4946     m_target_invariant = !m_name || m_name->TargetInvariant();
4947     m_source_invariant = !m_name || m_name->SourceInvariant();
4948 }
4949 
operator ==(const Condition & rhs) const4950 bool DesignHasHull::operator==(const Condition& rhs) const {
4951     if (this == &rhs)
4952         return true;
4953     if (typeid(*this) != typeid(rhs))
4954         return false;
4955 
4956     const DesignHasHull& rhs_ = static_cast<const DesignHasHull&>(rhs);
4957 
4958     CHECK_COND_VREF_MEMBER(m_name);
4959 
4960     return true;
4961 }
4962 
4963 namespace {
4964     struct DesignHasHullSimpleMatch {
DesignHasHullSimpleMatchCondition::__anone9a56faf4311::DesignHasHullSimpleMatch4965         explicit DesignHasHullSimpleMatch(const std::string& name) :
4966             m_name(name)
4967         {}
4968 
operator ()Condition::__anone9a56faf4311::DesignHasHullSimpleMatch4969         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
4970             if (!candidate)
4971                 return false;
4972 
4973             // is it a ship?
4974             auto ship = std::dynamic_pointer_cast<const ::Ship>(candidate);
4975             if (!ship)
4976                 return false;
4977             // with a valid design?
4978             const ShipDesign* design = ship->Design();
4979             if (!design)
4980                 return false;
4981 
4982             return design->Hull() == m_name;
4983         }
4984 
4985         const std::string&  m_name;
4986     };
4987 }
4988 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const4989 void DesignHasHull::Eval(const ScriptingContext& parent_context,
4990                          ObjectSet& matches, ObjectSet& non_matches,
4991                          SearchDomain search_domain/* = NON_MATCHES*/) const
4992 {
4993     bool simple_eval_safe = (!m_name || m_name->LocalCandidateInvariant()) &&
4994                             (parent_context.condition_root_candidate || RootCandidateInvariant());
4995     if (simple_eval_safe) {
4996         // evaluate number limits once, use to match all candidates
4997         std::string name = (m_name ? m_name->Eval(parent_context) : "");
4998 
4999         // need to test each candidate separately using EvalImpl and because the
5000         // design of the candidate object is tested
5001         EvalImpl(matches, non_matches, search_domain, DesignHasHullSimpleMatch(name));
5002     } else {
5003         // re-evaluate allowed turn range for each candidate object
5004         Condition::Eval(parent_context, matches, non_matches, search_domain);
5005     }
5006 }
5007 
Description(bool negated) const5008 std::string DesignHasHull::Description(bool negated/* = false*/) const {
5009     std::string name_str;
5010     if (m_name) {
5011         name_str = m_name->Description();
5012         if (m_name->ConstantExpr() && UserStringExists(name_str))
5013             name_str = UserString(name_str);
5014     }
5015     return str(FlexibleFormat((!negated)
5016         ? UserString("DESC_DESIGN_HAS_HULL")
5017         : UserString("DESC_DESIGN_HAS_HULL_NOT"))
5018         % name_str);
5019 }
5020 
Dump(unsigned short ntabs) const5021 std::string DesignHasHull::Dump(unsigned short ntabs) const {
5022     std::string retval = DumpIndent(ntabs) + "DesignHasHull";
5023     if (m_name)
5024         retval += " name = " + m_name->Dump(ntabs);
5025     retval += "\n";
5026     return retval;
5027 }
5028 
Match(const ScriptingContext & local_context) const5029 bool DesignHasHull::Match(const ScriptingContext& local_context) const {
5030     auto candidate = local_context.condition_local_candidate;
5031     if (!candidate) {
5032         ErrorLogger() << "DesignHasHull::Match passed no candidate object";
5033         return false;
5034     }
5035 
5036     std::string name = (m_name ? m_name->Eval(local_context) : "");
5037 
5038     return DesignHasHullSimpleMatch(name)(candidate);
5039 }
5040 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const5041 void DesignHasHull::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
5042                                                       ObjectSet& condition_non_targets) const
5043 {
5044     AddShipSet(parent_context.ContextObjects(), condition_non_targets);
5045 }
5046 
SetTopLevelContent(const std::string & content_name)5047 void DesignHasHull::SetTopLevelContent(const std::string& content_name) {
5048     if (m_name)
5049         m_name->SetTopLevelContent(content_name);
5050 }
5051 
GetCheckSum() const5052 unsigned int DesignHasHull::GetCheckSum() const {
5053     unsigned int retval{0};
5054 
5055     CheckSums::CheckSumCombine(retval, "Condition::DesignHasHull");
5056     CheckSums::CheckSumCombine(retval, m_name);
5057 
5058     TraceLogger() << "GetCheckSum(DesignHasHull): retval: " << retval;
5059     return retval;
5060 }
5061 
5062 ///////////////////////////////////////////////////////////
5063 // DesignHasPart                                         //
5064 ///////////////////////////////////////////////////////////
DesignHasPart(std::unique_ptr<ValueRef::ValueRef<std::string>> && name,std::unique_ptr<ValueRef::ValueRef<int>> && low,std::unique_ptr<ValueRef::ValueRef<int>> && high)5065 DesignHasPart::DesignHasPart(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name,
5066                              std::unique_ptr<ValueRef::ValueRef<int>>&& low,
5067                              std::unique_ptr<ValueRef::ValueRef<int>>&& high) :
5068     Condition(),
5069     m_low(std::move(low)),
5070     m_high(std::move(high)),
5071     m_name(std::move(name))
5072 {
5073     auto operands = {m_low.get(), m_high.get()};
5074     m_root_candidate_invariant =
5075         (!m_name || m_name->RootCandidateInvariant()) &&
5076         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
5077     m_target_invariant =
5078         (!m_name || m_name->TargetInvariant()) &&
5079         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
5080     m_source_invariant =
5081         (!m_name || m_name->SourceInvariant()) &&
5082         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
5083 }
5084 
operator ==(const Condition & rhs) const5085 bool DesignHasPart::operator==(const Condition& rhs) const {
5086     if (this == &rhs)
5087         return true;
5088     if (typeid(*this) != typeid(rhs))
5089         return false;
5090 
5091     const DesignHasPart& rhs_ = static_cast<const DesignHasPart&>(rhs);
5092 
5093     CHECK_COND_VREF_MEMBER(m_name);
5094     CHECK_COND_VREF_MEMBER(m_low)
5095     CHECK_COND_VREF_MEMBER(m_high)
5096 
5097     return true;
5098 }
5099 
5100 namespace {
5101     struct DesignHasPartSimpleMatch {
DesignHasPartSimpleMatchCondition::__anone9a56faf4711::DesignHasPartSimpleMatch5102         DesignHasPartSimpleMatch(int low, int high, const std::string& name, const ObjectMap& objects) :
5103             m_low(low),
5104             m_high(high),
5105             m_name(name),
5106             m_objects(objects)
5107         {}
5108 
operator ()Condition::__anone9a56faf4711::DesignHasPartSimpleMatch5109         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
5110             if (!candidate)
5111                 return false;
5112 
5113             std::shared_ptr<const Ship> ship = nullptr;
5114             if (auto fighter = std::dynamic_pointer_cast<const ::Fighter>(candidate)) {
5115                 // it is a fighter
5116                 ship = m_objects.get<Ship>(fighter->LaunchedFrom());
5117             } else {
5118                 ship = std::dynamic_pointer_cast<const ::Ship>(candidate);
5119             }
5120 
5121             // is it a ship
5122             if (!ship)
5123                 return false;
5124 
5125             // with a valid design?
5126             const ShipDesign* design = ship->Design();
5127             if (!design)
5128                 return false;
5129 
5130             int count = 0;
5131             for (const std::string& name : design->Parts()) {
5132                 if (name == m_name || (m_name.empty() && !name.empty()))
5133                     // number of copies of specified part,
5134                     // or total number of parts if no part name specified
5135                     ++count;
5136             }
5137             return (m_low <= count && count <= m_high);
5138         }
5139 
5140         int                 m_low;
5141         int                 m_high;
5142         const std::string&  m_name;
5143         const ObjectMap&    m_objects;
5144     };
5145 }
5146 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const5147 void DesignHasPart::Eval(const ScriptingContext& parent_context,
5148                          ObjectSet& matches, ObjectSet& non_matches,
5149                          SearchDomain search_domain/* = NON_MATCHES*/) const
5150 {
5151     bool simple_eval_safe = (!m_low || m_low->LocalCandidateInvariant()) &&
5152                             (!m_high || m_high->LocalCandidateInvariant()) &&
5153                             (!m_name || m_name->LocalCandidateInvariant()) &&
5154                             (parent_context.condition_root_candidate || RootCandidateInvariant());
5155     if (simple_eval_safe) {
5156         // evaluate number limits once, use to match all candidates
5157         std::string name = (m_name ? m_name->Eval(parent_context) : "");
5158         int low =          (m_low ? std::max(0, m_low->Eval(parent_context)) : 1);
5159         int high =         (m_high ? std::min(m_high->Eval(parent_context), INT_MAX) : INT_MAX);
5160 
5161         // need to test each candidate separately using EvalImpl and because the
5162         // design of the candidate object is tested
5163         EvalImpl(matches, non_matches, search_domain, DesignHasPartSimpleMatch(low, high, name, parent_context.ContextObjects()));
5164     } else {
5165         // re-evaluate allowed turn range for each candidate object
5166         Condition::Eval(parent_context, matches, non_matches, search_domain);
5167     }
5168 }
5169 
Description(bool negated) const5170 std::string DesignHasPart::Description(bool negated/* = false*/) const {
5171     std::string low_str = "1";
5172     if (m_low) {
5173         low_str = m_low->ConstantExpr() ?
5174                     std::to_string(m_low->Eval()) :
5175                     m_low->Description();
5176     }
5177     std::string high_str = std::to_string(INT_MAX);
5178     if (m_high) {
5179         high_str = m_high->ConstantExpr() ?
5180                     std::to_string(m_high->Eval()) :
5181                     m_high->Description();
5182     };
5183     std::string name_str;
5184     if (m_name) {
5185         name_str = m_name->Description();
5186         if (m_name->ConstantExpr() && UserStringExists(name_str))
5187             name_str = UserString(name_str);
5188     }
5189     return str(FlexibleFormat((!negated)
5190         ? UserString("DESC_DESIGN_HAS_PART")
5191         : UserString("DESC_DESIGN_HAS_PART_NOT"))
5192         % low_str
5193         % high_str
5194         % name_str);
5195 }
5196 
Dump(unsigned short ntabs) const5197 std::string DesignHasPart::Dump(unsigned short ntabs) const {
5198     std::string retval = DumpIndent(ntabs) + "DesignHasPart";
5199     if (m_low)
5200         retval += "low = " + m_low->Dump(ntabs);
5201     if (m_high)
5202         retval += " high = " + m_high->Dump(ntabs);
5203     if (m_name)
5204         retval += " name = " + m_name->Dump(ntabs);
5205     retval += "\n";
5206     return retval;
5207 }
5208 
Match(const ScriptingContext & local_context) const5209 bool DesignHasPart::Match(const ScriptingContext& local_context) const {
5210     auto candidate = local_context.condition_local_candidate;
5211     if (!candidate) {
5212         ErrorLogger() << "DesignHasPart::Match passed no candidate object";
5213         return false;
5214     }
5215 
5216     int low =  (m_low ? std::max(0, m_low->Eval(local_context)) : 0);
5217     int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN);
5218     std::string name = (m_name ? m_name->Eval(local_context) : "");
5219 
5220     return DesignHasPartSimpleMatch(low, high, name, local_context.ContextObjects())(candidate);
5221 }
5222 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const5223 void DesignHasPart::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
5224                                                       ObjectSet& condition_non_targets) const
5225 {
5226     AddShipSet(parent_context.ContextObjects(), condition_non_targets);
5227 }
5228 
SetTopLevelContent(const std::string & content_name)5229 void DesignHasPart::SetTopLevelContent(const std::string& content_name) {
5230     if (m_low)
5231         m_low->SetTopLevelContent(content_name);
5232     if (m_high)
5233         m_high->SetTopLevelContent(content_name);
5234     if (m_name)
5235         m_name->SetTopLevelContent(content_name);
5236 }
5237 
GetCheckSum() const5238 unsigned int DesignHasPart::GetCheckSum() const {
5239     unsigned int retval{0};
5240 
5241     CheckSums::CheckSumCombine(retval, "Condition::DesignHasPart");
5242     CheckSums::CheckSumCombine(retval, m_low);
5243     CheckSums::CheckSumCombine(retval, m_high);
5244     CheckSums::CheckSumCombine(retval, m_name);
5245 
5246     TraceLogger() << "GetCheckSum(DesignHasPart): retval: " << retval;
5247     return retval;
5248 }
5249 
5250 ///////////////////////////////////////////////////////////
5251 // DesignHasPartClass                                    //
5252 ///////////////////////////////////////////////////////////
DesignHasPartClass(ShipPartClass part_class,std::unique_ptr<ValueRef::ValueRef<int>> && low,std::unique_ptr<ValueRef::ValueRef<int>> && high)5253 DesignHasPartClass::DesignHasPartClass(ShipPartClass part_class,
5254                                        std::unique_ptr<ValueRef::ValueRef<int>>&& low,
5255                                        std::unique_ptr<ValueRef::ValueRef<int>>&& high) :
5256     Condition(),
5257     m_low(std::move(low)),
5258     m_high(std::move(high)),
5259     m_class(std::move(part_class))
5260 {
5261     auto operands = {m_low.get(), m_high.get()};
5262     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
5263     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
5264     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
5265 }
5266 
operator ==(const Condition & rhs) const5267 bool DesignHasPartClass::operator==(const Condition& rhs) const {
5268     if (this == &rhs)
5269         return true;
5270     if (typeid(*this) != typeid(rhs))
5271         return false;
5272 
5273     const DesignHasPartClass& rhs_ = static_cast<const DesignHasPartClass&>(rhs);
5274 
5275     if (m_class != rhs_.m_class)
5276         return false;
5277 
5278     CHECK_COND_VREF_MEMBER(m_low)
5279     CHECK_COND_VREF_MEMBER(m_high)
5280 
5281     return true;
5282 }
5283 
5284 namespace {
5285     struct DesignHasPartClassSimpleMatch {
DesignHasPartClassSimpleMatchCondition::__anone9a56faf4b11::DesignHasPartClassSimpleMatch5286         DesignHasPartClassSimpleMatch(int low, int high, ShipPartClass part_class) :
5287             m_low(low),
5288             m_high(high),
5289             m_part_class(part_class)
5290         {}
5291 
operator ()Condition::__anone9a56faf4b11::DesignHasPartClassSimpleMatch5292         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
5293             if (!candidate)
5294                 return false;
5295 
5296             // is it a ship?
5297             auto ship = std::dynamic_pointer_cast<const ::Ship>(candidate);
5298             if (!ship)
5299                 return false;
5300             // with a valid design?
5301             const ShipDesign* design = ship->Design();
5302             if (!design)
5303                 return false;
5304 
5305 
5306             int count = 0;
5307             for (const std::string& name : design->Parts()) {
5308                 if (const ShipPart* ship_part = GetShipPart(name)) {
5309                     if (ship_part->Class() == m_part_class)
5310                         ++count;
5311                 }
5312             }
5313             return (m_low <= count && count <= m_high);
5314         }
5315 
5316         int m_low;
5317         int m_high;
5318         ShipPartClass m_part_class;
5319     };
5320 }
5321 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const5322 void DesignHasPartClass::Eval(const ScriptingContext& parent_context,
5323                               ObjectSet& matches, ObjectSet& non_matches,
5324                               SearchDomain search_domain/* = NON_MATCHES*/) const
5325 {
5326     bool simple_eval_safe = (!m_low || m_low->LocalCandidateInvariant()) &&
5327                             (!m_high || m_high->LocalCandidateInvariant()) &&
5328                             (parent_context.condition_root_candidate || RootCandidateInvariant());
5329     if (simple_eval_safe) {
5330         // evaluate number limits once, use to match all candidates
5331         int low =          (m_low ? std::max(0, m_low->Eval(parent_context)) : 1);
5332         int high =         (m_high ? std::min(m_high->Eval(parent_context), INT_MAX) : INT_MAX);
5333 
5334         // need to test each candidate separately using EvalImpl and because the
5335         // design of the candidate object is tested
5336         EvalImpl(matches, non_matches, search_domain, DesignHasPartClassSimpleMatch(low, high, m_class));
5337     } else {
5338         // re-evaluate allowed turn range for each candidate object
5339         Condition::Eval(parent_context, matches, non_matches, search_domain);
5340     }
5341 }
5342 
Description(bool negated) const5343 std::string DesignHasPartClass::Description(bool negated/* = false*/) const {
5344     std::string low_str = "1";
5345     if (m_low) {
5346         low_str = m_low->ConstantExpr() ?
5347                     std::to_string(m_low->Eval()) :
5348                     m_low->Description();
5349     }
5350     std::string high_str = std::to_string(INT_MAX);
5351     if (m_high) {
5352         high_str = m_high->ConstantExpr() ?
5353                     std::to_string(m_high->Eval()) :
5354                     m_high->Description();
5355     }
5356     return str(FlexibleFormat((!negated)
5357         ? UserString("DESC_DESIGN_HAS_PART_CLASS")
5358         : UserString("DESC_DESIGN_HAS_PART_CLASS_NOT"))
5359                % low_str
5360                % high_str
5361                % UserString(boost::lexical_cast<std::string>(m_class)));
5362 }
5363 
Dump(unsigned short ntabs) const5364 std::string DesignHasPartClass::Dump(unsigned short ntabs) const {
5365     std::string retval = DumpIndent(ntabs) + "DesignHasPartClass";
5366     if (m_low)
5367         retval += " low = " + m_low->Dump(ntabs);
5368     if (m_high)
5369         retval += " high = " + m_high->Dump(ntabs);
5370     retval += " class = " + UserString(boost::lexical_cast<std::string>(m_class));
5371     retval += "\n";
5372     return retval;
5373 }
5374 
Match(const ScriptingContext & local_context) const5375 bool DesignHasPartClass::Match(const ScriptingContext& local_context) const {
5376     auto candidate = local_context.condition_local_candidate;
5377     if (!candidate) {
5378         ErrorLogger() << "DesignHasPartClass::Match passed no candidate object";
5379         return false;
5380     }
5381 
5382     int low =  (m_low ? m_low->Eval(local_context) : 0);
5383     int high = (m_high ? m_high->Eval(local_context) : INT_MAX);
5384 
5385     return DesignHasPartClassSimpleMatch(low, high, m_class)(candidate);
5386 }
5387 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const5388 void DesignHasPartClass::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
5389                                                            ObjectSet& condition_non_targets) const
5390 {
5391     AddShipSet(parent_context.ContextObjects(), condition_non_targets);
5392 }
5393 
SetTopLevelContent(const std::string & content_name)5394 void DesignHasPartClass::SetTopLevelContent(const std::string& content_name) {
5395     if (m_low)
5396         m_low->SetTopLevelContent(content_name);
5397     if (m_high)
5398         m_high->SetTopLevelContent(content_name);
5399 }
5400 
GetCheckSum() const5401 unsigned int DesignHasPartClass::GetCheckSum() const {
5402     unsigned int retval{0};
5403 
5404     CheckSums::CheckSumCombine(retval, "Condition::DesignHasPartClass");
5405     CheckSums::CheckSumCombine(retval, m_low);
5406     CheckSums::CheckSumCombine(retval, m_high);
5407     CheckSums::CheckSumCombine(retval, m_class);
5408 
5409     TraceLogger() << "GetCheckSum(DesignHasPartClass): retval: " << retval;
5410     return retval;
5411 }
5412 
5413 ///////////////////////////////////////////////////////////
5414 // PredefinedShipDesign                                  //
5415 ///////////////////////////////////////////////////////////
PredefinedShipDesign(std::unique_ptr<ValueRef::ValueRef<std::string>> && name)5416 PredefinedShipDesign::PredefinedShipDesign(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
5417     Condition(),
5418     m_name(std::move(name))
5419 {
5420     m_root_candidate_invariant = !m_name || m_name->RootCandidateInvariant();
5421     m_target_invariant = !m_name || m_name->TargetInvariant();
5422     m_source_invariant = !m_name || m_name->SourceInvariant();
5423 }
5424 
operator ==(const Condition & rhs) const5425 bool PredefinedShipDesign::operator==(const Condition& rhs) const {
5426     if (this == &rhs)
5427         return true;
5428     if (typeid(*this) != typeid(rhs))
5429         return false;
5430 
5431     const PredefinedShipDesign& rhs_ = static_cast<const PredefinedShipDesign&>(rhs);
5432 
5433     CHECK_COND_VREF_MEMBER(m_name)
5434 
5435     return true;
5436 }
5437 
5438 namespace {
5439     struct PredefinedShipDesignSimpleMatch {
PredefinedShipDesignSimpleMatchCondition::__anone9a56faf4c11::PredefinedShipDesignSimpleMatch5440         PredefinedShipDesignSimpleMatch() :
5441             m_any_predef_design_ok(true),
5442             m_name()
5443         {}
5444 
PredefinedShipDesignSimpleMatchCondition::__anone9a56faf4c11::PredefinedShipDesignSimpleMatch5445         PredefinedShipDesignSimpleMatch(const std::string& name) :
5446             m_any_predef_design_ok(false),
5447             m_name(name)
5448         {}
5449 
operator ()Condition::__anone9a56faf4c11::PredefinedShipDesignSimpleMatch5450         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
5451             auto ship = std::dynamic_pointer_cast<const Ship>(candidate);
5452             if (!ship)
5453                 return false;
5454             const ShipDesign* candidate_design = ship->Design();
5455             if (!candidate_design)
5456                 return false;
5457 
5458             // ship has a valid design.  see if it is / could be a predefined ship design...
5459 
5460             // all predefined named designs are hard-coded in parsing to have a designed on turn 0 (before first turn)
5461             if (candidate_design->DesignedOnTurn() != 0)
5462                 return false;
5463 
5464             if (m_any_predef_design_ok)
5465                 return true;    // any predefined design is OK; don't need to check name.
5466 
5467             return (m_name == candidate_design->Name(false)); // don't look up in stringtable; predefined designs are stored by stringtable entry key
5468         }
5469 
5470         bool        m_any_predef_design_ok;
5471         std::string m_name;
5472     };
5473 }
5474 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const5475 void PredefinedShipDesign::Eval(const ScriptingContext& parent_context,
5476                                 ObjectSet& matches, ObjectSet& non_matches,
5477                                 SearchDomain search_domain/* = NON_MATCHES*/) const
5478 {
5479     bool simple_eval_safe = (!m_name || m_name->LocalCandidateInvariant()) &&
5480                             (parent_context.condition_root_candidate || RootCandidateInvariant());
5481     if (simple_eval_safe) {
5482         // testing each candidate to see if its design is predefined or is a
5483         // particular named predefined design
5484         if (!m_name) {
5485             EvalImpl(matches, non_matches, search_domain, PredefinedShipDesignSimpleMatch());
5486         } else {
5487             std::string name = m_name->Eval(parent_context);
5488             EvalImpl(matches, non_matches, search_domain, PredefinedShipDesignSimpleMatch(name));
5489         }
5490     } else {
5491         // re-evaluate allowed turn range for each candidate object
5492         Condition::Eval(parent_context, matches, non_matches, search_domain);
5493     }
5494 }
5495 
Description(bool negated) const5496 std::string PredefinedShipDesign::Description(bool negated/* = false*/) const {
5497     std::string name_str;
5498     if (m_name) {
5499         name_str = m_name->Description();
5500         if (m_name->ConstantExpr() && UserStringExists(name_str))
5501             name_str = UserString(name_str);
5502     }
5503     return str(FlexibleFormat((!negated)
5504         ? UserString("DESC_PREDEFINED_SHIP_DESIGN")
5505         : UserString("DESC_PREDEFINED_SHIP_DESIGN_NOT"))
5506         % name_str);
5507 }
5508 
Dump(unsigned short ntabs) const5509 std::string PredefinedShipDesign::Dump(unsigned short ntabs) const {
5510     std::string retval = DumpIndent(ntabs) + "PredefinedShipDesign";
5511     if (m_name)
5512         retval += " name = " + m_name->Dump(ntabs);
5513     retval += "\n";
5514     return retval;
5515 }
5516 
Match(const ScriptingContext & local_context) const5517 bool PredefinedShipDesign::Match(const ScriptingContext& local_context) const {
5518     auto candidate = local_context.condition_local_candidate;
5519     if (!candidate) {
5520         ErrorLogger() << "PredefinedShipDesign::Match passed no candidate object";
5521         return false;
5522     }
5523 
5524     if (!m_name)
5525         return PredefinedShipDesignSimpleMatch()(candidate);
5526 
5527     std::string name = m_name->Eval(local_context);
5528     return PredefinedShipDesignSimpleMatch(name)(candidate);
5529 }
5530 
SetTopLevelContent(const std::string & content_name)5531 void PredefinedShipDesign::SetTopLevelContent(const std::string& content_name) {
5532     if (m_name)
5533         m_name->SetTopLevelContent(content_name);
5534 }
5535 
GetCheckSum() const5536 unsigned int PredefinedShipDesign::GetCheckSum() const {
5537     unsigned int retval{0};
5538 
5539     CheckSums::CheckSumCombine(retval, "Condition::PredefinedShipDesign");
5540     CheckSums::CheckSumCombine(retval, m_name);
5541 
5542     TraceLogger() << "GetCheckSum(PredefinedShipDesign): retval: " << retval;
5543     return retval;
5544 }
5545 
5546 ///////////////////////////////////////////////////////////
5547 // NumberedShipDesign                                    //
5548 ///////////////////////////////////////////////////////////
NumberedShipDesign(std::unique_ptr<ValueRef::ValueRef<int>> && design_id)5549 NumberedShipDesign::NumberedShipDesign(std::unique_ptr<ValueRef::ValueRef<int>>&& design_id) :
5550     Condition(),
5551     m_design_id(std::move(design_id))
5552 {
5553     m_root_candidate_invariant = !m_design_id || m_design_id->RootCandidateInvariant();
5554     m_target_invariant = !m_design_id || m_design_id->TargetInvariant();
5555     m_source_invariant = !m_design_id || m_design_id->SourceInvariant();
5556 }
5557 
operator ==(const Condition & rhs) const5558 bool NumberedShipDesign::operator==(const Condition& rhs) const {
5559     if (this == &rhs)
5560         return true;
5561     if (typeid(*this) != typeid(rhs))
5562         return false;
5563 
5564     const NumberedShipDesign& rhs_ = static_cast<const NumberedShipDesign&>(rhs);
5565 
5566     CHECK_COND_VREF_MEMBER(m_design_id)
5567 
5568     return true;
5569 }
5570 
5571 namespace {
5572     struct NumberedShipDesignSimpleMatch {
NumberedShipDesignSimpleMatchCondition::__anone9a56faf4d11::NumberedShipDesignSimpleMatch5573         NumberedShipDesignSimpleMatch(int design_id) :
5574             m_design_id(design_id)
5575         {}
5576 
operator ()Condition::__anone9a56faf4d11::NumberedShipDesignSimpleMatch5577         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
5578             if (!candidate)
5579                 return false;
5580             if (m_design_id == INVALID_DESIGN_ID)
5581                 return false;
5582             if (auto ship = std::dynamic_pointer_cast<const Ship>(candidate))
5583                 if (ship->DesignID() == m_design_id)
5584                     return true;
5585             return false;
5586         }
5587 
5588         int m_design_id;
5589     };
5590 }
5591 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const5592 void NumberedShipDesign::Eval(const ScriptingContext& parent_context,
5593                               ObjectSet& matches, ObjectSet& non_matches,
5594                               SearchDomain search_domain/* = NON_MATCHES*/) const
5595 {
5596     bool simple_eval_safe = m_design_id->ConstantExpr() ||
5597                             (m_design_id->LocalCandidateInvariant() &&
5598                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
5599     if (simple_eval_safe) {
5600         // evaluate design id once, and use to check all candidate objects
5601         int design_id = m_design_id->Eval(parent_context);
5602 
5603         // design of the candidate objects is tested, so need to check each separately
5604         EvalImpl(matches, non_matches, search_domain, NumberedShipDesignSimpleMatch(design_id));
5605     } else {
5606         // re-evaluate design id for each candidate object
5607         Condition::Eval(parent_context, matches, non_matches, search_domain);
5608     }
5609 }
5610 
Description(bool negated) const5611 std::string NumberedShipDesign::Description(bool negated/* = false*/) const {
5612     std::string id_str = m_design_id->ConstantExpr() ?
5613                             std::to_string(m_design_id->Eval()) :
5614                             m_design_id->Description();
5615 
5616     return str(FlexibleFormat((!negated)
5617         ? UserString("DESC_NUMBERED_SHIP_DESIGN")
5618         : UserString("DESC_NUMBERED_SHIP_DESIGN_NOT"))
5619                % id_str);
5620 }
5621 
Dump(unsigned short ntabs) const5622 std::string NumberedShipDesign::Dump(unsigned short ntabs) const
5623 { return DumpIndent(ntabs) + "NumberedShipDesign design_id = " + m_design_id->Dump(ntabs); }
5624 
Match(const ScriptingContext & local_context) const5625 bool NumberedShipDesign::Match(const ScriptingContext& local_context) const {
5626     auto candidate = local_context.condition_local_candidate;
5627     if (!candidate) {
5628         ErrorLogger() << "NumberedShipDesign::Match passed no candidate object";
5629         return false;
5630     }
5631     return NumberedShipDesignSimpleMatch(m_design_id->Eval(local_context))(candidate);
5632 }
5633 
SetTopLevelContent(const std::string & content_name)5634 void NumberedShipDesign::SetTopLevelContent(const std::string& content_name) {
5635     if (m_design_id)
5636         m_design_id->SetTopLevelContent(content_name);
5637 }
5638 
GetCheckSum() const5639 unsigned int NumberedShipDesign::GetCheckSum() const {
5640     unsigned int retval{0};
5641 
5642     CheckSums::CheckSumCombine(retval, "Condition::NumberedShipDesign");
5643     CheckSums::CheckSumCombine(retval, m_design_id);
5644 
5645     TraceLogger() << "GetCheckSum(NumberedShipDesign): retval: " << retval;
5646     return retval;
5647 }
5648 
5649 ///////////////////////////////////////////////////////////
5650 // ProducedByEmpire                                      //
5651 ///////////////////////////////////////////////////////////
ProducedByEmpire(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id)5652 ProducedByEmpire::ProducedByEmpire(std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id) :
5653     Condition(),
5654     m_empire_id(std::move(empire_id))
5655 {
5656     m_root_candidate_invariant = !m_empire_id || m_empire_id->RootCandidateInvariant();
5657     m_target_invariant = !m_empire_id || m_empire_id->TargetInvariant();
5658     m_source_invariant = !m_empire_id || m_empire_id->SourceInvariant();
5659 }
5660 
operator ==(const Condition & rhs) const5661 bool ProducedByEmpire::operator==(const Condition& rhs) const {
5662     if (this == &rhs)
5663         return true;
5664     if (typeid(*this) != typeid(rhs))
5665         return false;
5666 
5667     const ProducedByEmpire& rhs_ = static_cast<const ProducedByEmpire&>(rhs);
5668 
5669     CHECK_COND_VREF_MEMBER(m_empire_id)
5670 
5671     return true;
5672 }
5673 
5674 namespace {
5675     struct ProducedByEmpireSimpleMatch {
ProducedByEmpireSimpleMatchCondition::__anone9a56faf4e11::ProducedByEmpireSimpleMatch5676         ProducedByEmpireSimpleMatch(int empire_id) :
5677             m_empire_id(empire_id)
5678         {}
5679 
operator ()Condition::__anone9a56faf4e11::ProducedByEmpireSimpleMatch5680         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
5681             if (!candidate)
5682                 return false;
5683 
5684             if (auto ship = std::dynamic_pointer_cast<const ::Ship>(candidate))
5685                 return ship->ProducedByEmpireID() == m_empire_id;
5686             else if (auto building = std::dynamic_pointer_cast<const ::Building>(candidate))
5687                 return building->ProducedByEmpireID() == m_empire_id;
5688             return false;
5689         }
5690 
5691         int m_empire_id;
5692     };
5693 }
5694 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const5695 void ProducedByEmpire::Eval(const ScriptingContext& parent_context,
5696                             ObjectSet& matches, ObjectSet& non_matches,
5697                             SearchDomain search_domain/* = NON_MATCHES*/) const
5698 {
5699     bool simple_eval_safe = m_empire_id->ConstantExpr() ||
5700                             (m_empire_id->LocalCandidateInvariant() &&
5701                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
5702     if (simple_eval_safe) {
5703         // evaluate empire id once, and use to check all candidate objects
5704         int empire_id = m_empire_id->Eval(parent_context);
5705         EvalImpl(matches, non_matches, search_domain, ProducedByEmpireSimpleMatch(empire_id));
5706     } else {
5707         // re-evaluate empire id for each candidate object
5708         Condition::Eval(parent_context, matches, non_matches, search_domain);
5709     }
5710 }
5711 
Description(bool negated) const5712 std::string ProducedByEmpire::Description(bool negated/* = false*/) const {
5713     std::string empire_str;
5714     if (m_empire_id) {
5715         int empire_id = ALL_EMPIRES;
5716         if (m_empire_id->ConstantExpr())
5717             empire_id = m_empire_id->Eval();
5718         if (const Empire* empire = GetEmpire(empire_id))
5719             empire_str = empire->Name();
5720         else
5721             empire_str = m_empire_id->Description();
5722     }
5723 
5724     return str(FlexibleFormat((!negated)
5725         ? UserString("DESC_PRODUCED_BY_EMPIRE")
5726         : UserString("DESC_PRODUCED_BY_EMPIRE_NOT"))
5727                % empire_str);
5728 }
5729 
Dump(unsigned short ntabs) const5730 std::string ProducedByEmpire::Dump(unsigned short ntabs) const
5731 { return DumpIndent(ntabs) + "ProducedByEmpire empire_id = " + m_empire_id->Dump(ntabs); }
5732 
Match(const ScriptingContext & local_context) const5733 bool ProducedByEmpire::Match(const ScriptingContext& local_context) const {
5734     auto candidate = local_context.condition_local_candidate;
5735     if (!candidate) {
5736         ErrorLogger() << "ProducedByEmpire::Match passed no candidate object";
5737         return false;
5738     }
5739 
5740     return ProducedByEmpireSimpleMatch(m_empire_id->Eval(local_context))(candidate);
5741 }
5742 
SetTopLevelContent(const std::string & content_name)5743 void ProducedByEmpire::SetTopLevelContent(const std::string& content_name) {
5744     if (m_empire_id)
5745         m_empire_id->SetTopLevelContent(content_name);
5746 }
5747 
GetCheckSum() const5748 unsigned int ProducedByEmpire::GetCheckSum() const {
5749     unsigned int retval{0};
5750 
5751     CheckSums::CheckSumCombine(retval, "Condition::ProducedByEmpire");
5752     CheckSums::CheckSumCombine(retval, m_empire_id);
5753 
5754     TraceLogger() << "GetCheckSum(ProducedByEmpire): retval: " << retval;
5755     return retval;
5756 }
5757 
5758 ///////////////////////////////////////////////////////////
5759 // Chance                                                //
5760 ///////////////////////////////////////////////////////////
Chance(std::unique_ptr<ValueRef::ValueRef<double>> && chance)5761 Chance::Chance(std::unique_ptr<ValueRef::ValueRef<double>>&& chance) :
5762     Condition(),
5763     m_chance(std::move(chance))
5764 {
5765     m_root_candidate_invariant = !m_chance || m_chance->RootCandidateInvariant();
5766     m_target_invariant = !m_chance || m_chance->TargetInvariant();
5767     m_source_invariant = !m_chance || m_chance->SourceInvariant();
5768 }
5769 
operator ==(const Condition & rhs) const5770 bool Chance::operator==(const Condition& rhs) const {
5771     if (this == &rhs)
5772         return true;
5773     if (typeid(*this) != typeid(rhs))
5774         return false;
5775 
5776     const Chance& rhs_ = static_cast<const Chance&>(rhs);
5777 
5778     CHECK_COND_VREF_MEMBER(m_chance)
5779 
5780     return true;
5781 }
5782 
5783 namespace {
5784     struct ChanceSimpleMatch {
ChanceSimpleMatchCondition::__anone9a56faf4f11::ChanceSimpleMatch5785         ChanceSimpleMatch(float chance) :
5786             m_chance(chance)
5787         {}
5788 
operator ()Condition::__anone9a56faf4f11::ChanceSimpleMatch5789         bool operator()(std::shared_ptr<const UniverseObject> candidate) const
5790         { return RandZeroToOne() <= m_chance; }
5791 
5792         float m_chance;
5793     };
5794 }
5795 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const5796 void Chance::Eval(const ScriptingContext& parent_context,
5797                   ObjectSet& matches, ObjectSet& non_matches,
5798                   SearchDomain search_domain/* = NON_MATCHES*/) const
5799 {
5800     bool simple_eval_safe = m_chance->ConstantExpr() ||
5801                             (m_chance->LocalCandidateInvariant() &&
5802                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
5803     if (simple_eval_safe) {
5804         // evaluate empire id once, and use to check all candidate objects
5805         float chance = std::max(0.0, std::min(1.0, m_chance->Eval(parent_context)));
5806         // chance is tested independently for each candidate object
5807         EvalImpl(matches, non_matches, search_domain, ChanceSimpleMatch(chance));
5808     } else {
5809         // re-evaluate empire id for each candidate object
5810         Condition::Eval(parent_context, matches, non_matches, search_domain);
5811     }
5812 }
5813 
Description(bool negated) const5814 std::string Chance::Description(bool negated/* = false*/) const {
5815     if (m_chance->ConstantExpr()) {
5816         return str(FlexibleFormat((!negated)
5817             ? UserString("DESC_CHANCE_PERCENTAGE")
5818             : UserString("DESC_CHANCE_PERCENTAGE_NOT"))
5819                 % std::to_string(std::max(0.0, std::min(m_chance->Eval(), 1.0)) * 100));
5820     } else {
5821         return str(FlexibleFormat((!negated)
5822             ? UserString("DESC_CHANCE")
5823             : UserString("DESC_CHANCE_NOT"))
5824             % m_chance->Description());
5825     }
5826 }
5827 
Dump(unsigned short ntabs) const5828 std::string Chance::Dump(unsigned short ntabs) const
5829 { return DumpIndent(ntabs) + "Random probability = " + m_chance->Dump(ntabs) + "\n"; }
5830 
Match(const ScriptingContext & local_context) const5831 bool Chance::Match(const ScriptingContext& local_context) const {
5832     float chance = std::max(0.0, std::min(m_chance->Eval(local_context), 1.0));
5833     return RandZeroToOne() <= chance;
5834 }
5835 
SetTopLevelContent(const std::string & content_name)5836 void Chance::SetTopLevelContent(const std::string& content_name) {
5837     if (m_chance)
5838         m_chance->SetTopLevelContent(content_name);
5839 }
5840 
GetCheckSum() const5841 unsigned int Chance::GetCheckSum() const {
5842     unsigned int retval{0};
5843 
5844     CheckSums::CheckSumCombine(retval, "Condition::Chance");
5845     CheckSums::CheckSumCombine(retval, m_chance);
5846 
5847     TraceLogger() << "GetCheckSum(Chance): retval: " << retval;
5848     return retval;
5849 }
5850 
5851 ///////////////////////////////////////////////////////////
5852 // MeterValue                                            //
5853 ///////////////////////////////////////////////////////////
MeterValue(MeterType meter,std::unique_ptr<ValueRef::ValueRef<double>> && low,std::unique_ptr<ValueRef::ValueRef<double>> && high)5854 MeterValue::MeterValue(MeterType meter,
5855                        std::unique_ptr<ValueRef::ValueRef<double>>&& low,
5856                        std::unique_ptr<ValueRef::ValueRef<double>>&& high) :
5857     Condition(),
5858     m_meter(meter),
5859     m_low(std::move(low)),
5860     m_high(std::move(high))
5861 {
5862     auto operands = {m_low.get(), m_high.get()};
5863     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
5864     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
5865     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
5866 }
5867 
operator ==(const Condition & rhs) const5868 bool MeterValue::operator==(const Condition& rhs) const {
5869     if (this == &rhs)
5870         return true;
5871     if (typeid(*this) != typeid(rhs))
5872         return false;
5873 
5874     const MeterValue& rhs_ = static_cast<const MeterValue&>(rhs);
5875 
5876     if (m_meter != rhs_.m_meter)
5877         return false;
5878 
5879     CHECK_COND_VREF_MEMBER(m_low)
5880     CHECK_COND_VREF_MEMBER(m_high)
5881 
5882     return true;
5883 }
5884 
5885 namespace {
5886     struct MeterValueSimpleMatch {
MeterValueSimpleMatchCondition::__anone9a56faf5311::MeterValueSimpleMatch5887         MeterValueSimpleMatch(float low, float high, MeterType meter_type) :
5888             m_low(low),
5889             m_high(high),
5890             m_meter_type(meter_type)
5891         {}
5892 
operator ()Condition::__anone9a56faf5311::MeterValueSimpleMatch5893         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
5894             if (!candidate)
5895                 return false;
5896 
5897             if (const Meter* meter = candidate->GetMeter(m_meter_type)) {
5898                 float value = meter->Initial();    // match Initial rather than Current to make results reproducible in a given turn, until back propagation happens
5899                 return m_low <= value && value <= m_high;
5900             }
5901 
5902             return false;
5903         }
5904 
5905         float m_low;
5906         float m_high;
5907         MeterType m_meter_type;
5908     };
5909 
MeterTypeDumpString(MeterType meter)5910     std::string MeterTypeDumpString(MeterType meter) {
5911         switch (meter) {
5912         case INVALID_METER_TYPE:        return "INVALID_METER_TYPE"; break;
5913         case METER_TARGET_POPULATION:   return "TargetPopulation";   break;
5914         case METER_TARGET_INDUSTRY:     return "TargetIndustry";     break;
5915         case METER_TARGET_RESEARCH:     return "TargetResearch";     break;
5916         case METER_TARGET_TRADE:        return "TargetTrade";        break;
5917         case METER_TARGET_CONSTRUCTION: return "TargetConstruction"; break;
5918         case METER_TARGET_HAPPINESS:    return "TargetHappiness";    break;
5919         case METER_MAX_CAPACITY:        return "MaxCapacity";        break;
5920         case METER_MAX_SECONDARY_STAT:  return "MaxSecondaryStat";   break;
5921         case METER_MAX_FUEL:            return "MaxFuel";            break;
5922         case METER_MAX_SHIELD:          return "MaxShield";          break;
5923         case METER_MAX_STRUCTURE:       return "MaxStructure";       break;
5924         case METER_MAX_DEFENSE:         return "MaxDefense";         break;
5925         case METER_MAX_SUPPLY:          return "MaxSupply";          break;
5926         case METER_MAX_STOCKPILE:       return "MaxStockpile";       break;
5927         case METER_MAX_TROOPS:          return "MaxTroops";          break;
5928         case METER_POPULATION:          return "Population";         break;
5929         case METER_INDUSTRY:            return "Industry";           break;
5930         case METER_RESEARCH:            return "Research";           break;
5931         case METER_TRADE:               return "Trade";              break;
5932         case METER_CONSTRUCTION:        return "Construction";       break;
5933         case METER_HAPPINESS:           return "Happiness";          break;
5934         case METER_CAPACITY:            return "Capacity";           break;
5935         case METER_SECONDARY_STAT:      return "SecondaryStat";      break;
5936         case METER_FUEL:                return "Fuel";               break;
5937         case METER_SHIELD:              return "Shield";             break;
5938         case METER_STRUCTURE:           return "Structure";          break;
5939         case METER_DEFENSE:             return "Defense";            break;
5940         case METER_SUPPLY:              return "Supply";             break;
5941         case METER_STOCKPILE:           return "Stockpile";          break;
5942         case METER_TROOPS:              return "Troops";             break;
5943         case METER_REBEL_TROOPS:        return "RebelTroops";        break;
5944         case METER_SIZE:                return "Size";               break;
5945         case METER_STEALTH:             return "Stealth";            break;
5946         case METER_DETECTION:           return "Detection";          break;
5947         case METER_SPEED:               return "Speed";              break;
5948         default:                        return "?Meter?";            break;
5949         }
5950     }
5951 }
5952 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const5953 void MeterValue::Eval(const ScriptingContext& parent_context,
5954                       ObjectSet& matches, ObjectSet& non_matches,
5955                       SearchDomain search_domain/* = NON_MATCHES*/) const
5956 {
5957     bool simple_eval_safe = ((!m_low || m_low->LocalCandidateInvariant()) &&
5958                              (!m_high || m_high->LocalCandidateInvariant()) &&
5959                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
5960     if (simple_eval_safe) {
5961         // evaluate number limits once, use to match all candidates
5962         float low = (m_low ? m_low->Eval(parent_context) : -Meter::LARGE_VALUE);
5963         float high = (m_high ? m_high->Eval(parent_context) : Meter::LARGE_VALUE);
5964         EvalImpl(matches, non_matches, search_domain, MeterValueSimpleMatch(low, high, m_meter));
5965     } else {
5966         // re-evaluate allowed turn range for each candidate object
5967         Condition::Eval(parent_context, matches, non_matches, search_domain);
5968     }
5969 }
5970 
Description(bool negated) const5971 std::string MeterValue::Description(bool negated/* = false*/) const {
5972     std::string low_str = (m_low ? (m_low->ConstantExpr() ?
5973                                     std::to_string(m_low->Eval()) :
5974                                     m_low->Description())
5975                                  : std::to_string(-Meter::LARGE_VALUE));
5976     std::string high_str = (m_high ? (m_high->ConstantExpr() ?
5977                                       std::to_string(m_high->Eval()) :
5978                                       m_high->Description())
5979                                    : std::to_string(Meter::LARGE_VALUE));
5980 
5981     if (m_low && !m_high) {
5982         return str(FlexibleFormat((!negated) ?
5983                                     UserString("DESC_METER_VALUE_CURRENT_MIN") :
5984                                     UserString("DESC_METER_VALUE_CURRENT_MIN_NOT"))
5985             % UserString(boost::lexical_cast<std::string>(m_meter))
5986             % low_str);
5987     } else if (m_high && !m_low) {
5988         return str(FlexibleFormat((!negated) ?
5989                                     UserString("DESC_METER_VALUE_CURRENT_MAX") :
5990                                     UserString("DESC_METER_VALUE_CURRENT_MAX_NOT"))
5991             % UserString(boost::lexical_cast<std::string>(m_meter))
5992             % high_str);
5993     } else {
5994         return str(FlexibleFormat((!negated) ?
5995                                     UserString("DESC_METER_VALUE_CURRENT") :
5996                                     UserString("DESC_METER_VALUE_CURRENT_NOT"))
5997             % UserString(boost::lexical_cast<std::string>(m_meter))
5998             % low_str
5999             % high_str);
6000     }
6001 }
6002 
Dump(unsigned short ntabs) const6003 std::string MeterValue::Dump(unsigned short ntabs) const {
6004     std::string retval = DumpIndent(ntabs);
6005     retval += MeterTypeDumpString(m_meter);
6006     if (m_low)
6007         retval += " low = " + m_low->Dump(ntabs);
6008     if (m_high)
6009         retval += " high = " + m_high->Dump(ntabs);
6010     retval += "\n";
6011     return retval;
6012 }
6013 
Match(const ScriptingContext & local_context) const6014 bool MeterValue::Match(const ScriptingContext& local_context) const {
6015     auto candidate = local_context.condition_local_candidate;
6016     if (!candidate) {
6017         ErrorLogger() << "MeterValue::Match passed no candidate object";
6018         return false;
6019     }
6020     float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE);
6021     float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE);
6022     return MeterValueSimpleMatch(low, high, m_meter)(candidate);
6023 }
6024 
SetTopLevelContent(const std::string & content_name)6025 void MeterValue::SetTopLevelContent(const std::string& content_name) {
6026     if (m_low)
6027         m_low->SetTopLevelContent(content_name);
6028     if (m_high)
6029         m_high->SetTopLevelContent(content_name);
6030 }
6031 
GetCheckSum() const6032 unsigned int MeterValue::GetCheckSum() const {
6033     unsigned int retval{0};
6034 
6035     CheckSums::CheckSumCombine(retval, "Condition::MeterValue");
6036     CheckSums::CheckSumCombine(retval, m_meter);
6037     CheckSums::CheckSumCombine(retval, m_low);
6038     CheckSums::CheckSumCombine(retval, m_high);
6039 
6040     TraceLogger() << "GetCheckSum(MeterValue): retval: " << retval;
6041     return retval;
6042 }
6043 
6044 ///////////////////////////////////////////////////////////
6045 // ShipPartMeterValue                                    //
6046 ///////////////////////////////////////////////////////////
ShipPartMeterValue(std::unique_ptr<ValueRef::ValueRef<std::string>> && ship_part_name,MeterType meter,std::unique_ptr<ValueRef::ValueRef<double>> && low,std::unique_ptr<ValueRef::ValueRef<double>> && high)6047 ShipPartMeterValue::ShipPartMeterValue(std::unique_ptr<ValueRef::ValueRef<std::string>>&& ship_part_name,
6048                                        MeterType meter,
6049                                        std::unique_ptr<ValueRef::ValueRef<double>>&& low,
6050                                        std::unique_ptr<ValueRef::ValueRef<double>>&& high) :
6051     Condition(),
6052     m_part_name(std::move(ship_part_name)),
6053     m_meter(meter),
6054     m_low(std::move(low)),
6055     m_high(std::move(high))
6056 {
6057     auto operands = {m_low.get(), m_high.get()};
6058     m_root_candidate_invariant =
6059         (!m_part_name || m_part_name->RootCandidateInvariant()) &&
6060         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
6061     m_target_invariant =
6062         (!m_part_name || m_part_name->TargetInvariant()) &&
6063         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
6064     m_source_invariant =
6065         (!m_part_name || m_part_name->SourceInvariant()) &&
6066         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
6067 }
6068 
operator ==(const Condition & rhs) const6069 bool ShipPartMeterValue::operator==(const Condition& rhs) const {
6070     if (this == &rhs)
6071         return true;
6072     if (typeid(*this) != typeid(rhs))
6073         return false;
6074 
6075     const ShipPartMeterValue& rhs_ = static_cast<const ShipPartMeterValue&>(rhs);
6076 
6077     if (m_meter != rhs_.m_meter)
6078         return false;
6079 
6080     CHECK_COND_VREF_MEMBER(m_part_name)
6081     CHECK_COND_VREF_MEMBER(m_low)
6082     CHECK_COND_VREF_MEMBER(m_high)
6083 
6084     return true;
6085 }
6086 
6087 namespace {
6088     struct ShipPartMeterValueSimpleMatch {
ShipPartMeterValueSimpleMatchCondition::__anone9a56faf5711::ShipPartMeterValueSimpleMatch6089         ShipPartMeterValueSimpleMatch(const std::string& ship_part_name,
6090                                       MeterType meter, float low, float high) :
6091             m_part_name(ship_part_name),
6092             m_low(low),
6093             m_high(high),
6094             m_meter(meter)
6095         {}
6096 
operator ()Condition::__anone9a56faf5711::ShipPartMeterValueSimpleMatch6097         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
6098             if (!candidate)
6099                 return false;
6100             auto ship = std::dynamic_pointer_cast<const Ship>(candidate);
6101             if (!ship)
6102                 return false;
6103             const Meter* meter = ship->GetPartMeter(m_meter, m_part_name);
6104             if (!meter)
6105                 return false;
6106             float meter_current = meter->Current();
6107             return (m_low <= meter_current && meter_current <= m_high);
6108         }
6109 
6110         std::string m_part_name;
6111         float       m_low;
6112         float       m_high;
6113         MeterType   m_meter;
6114     };
6115 }
6116 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const6117 void ShipPartMeterValue::Eval(const ScriptingContext& parent_context,
6118                               ObjectSet& matches, ObjectSet& non_matches,
6119                               SearchDomain search_domain/* = NON_MATCHES*/) const
6120 {
6121     bool simple_eval_safe = ((!m_part_name || m_part_name->LocalCandidateInvariant()) &&
6122                              (!m_low || m_low->LocalCandidateInvariant()) &&
6123                              (!m_high || m_high->LocalCandidateInvariant()) &&
6124                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
6125     if (simple_eval_safe) {
6126         // evaluate number limits once, use to match all candidates
6127         float low = (m_low ? m_low->Eval(parent_context) : -Meter::LARGE_VALUE);
6128         float high = (m_high ? m_high->Eval(parent_context) : Meter::LARGE_VALUE);
6129         std::string part_name = (m_part_name ? m_part_name->Eval(parent_context) : "");
6130         EvalImpl(matches, non_matches, search_domain, ShipPartMeterValueSimpleMatch(part_name, m_meter, low, high));
6131     } else {
6132         // re-evaluate allowed turn range for each candidate object
6133         Condition::Eval(parent_context, matches, non_matches, search_domain);
6134     }
6135 }
6136 
Description(bool negated) const6137 std::string ShipPartMeterValue::Description(bool negated/* = false*/) const {
6138     std::string low_str;
6139     if (m_low)
6140         low_str = m_low->Description();
6141     else
6142         low_str = std::to_string(-Meter::LARGE_VALUE);
6143 
6144     std::string high_str;
6145     if (m_high)
6146         high_str = m_high->Description();
6147     else
6148         high_str = std::to_string(Meter::LARGE_VALUE);
6149 
6150     std::string part_str;
6151     if (m_part_name) {
6152         part_str = m_part_name->Description();
6153         if (m_part_name->ConstantExpr() && UserStringExists(part_str))
6154             part_str = UserString(part_str);
6155     }
6156 
6157     return str(FlexibleFormat((!negated)
6158         ? UserString("DESC_SHIP_PART_METER_VALUE_CURRENT")
6159         : UserString("DESC_SHIP_PART_METER_VALUE_CURRENT_NOT"))
6160                % UserString(boost::lexical_cast<std::string>(m_meter))
6161                % part_str
6162                % low_str
6163                % high_str);
6164 }
6165 
Dump(unsigned short ntabs) const6166 std::string ShipPartMeterValue::Dump(unsigned short ntabs) const {
6167     std::string retval = DumpIndent(ntabs);
6168     retval += MeterTypeDumpString(m_meter);
6169     if (m_part_name)
6170         retval += " part = " + m_part_name->Dump(ntabs);
6171     if (m_low)
6172         retval += " low = " + m_low->Dump(ntabs);
6173     if (m_high)
6174         retval += " high = " + m_high->Dump(ntabs);
6175     retval += "\n";
6176     return retval;
6177 }
6178 
Match(const ScriptingContext & local_context) const6179 bool ShipPartMeterValue::Match(const ScriptingContext& local_context) const {
6180     auto candidate = local_context.condition_local_candidate;
6181     if (!candidate) {
6182         ErrorLogger() << "ShipPartMeterValue::Match passed no candidate object";
6183         return false;
6184     }
6185     float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE);
6186     float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE);
6187     std::string part_name = (m_part_name ? m_part_name->Eval(local_context) : "");
6188     return ShipPartMeterValueSimpleMatch(part_name, m_meter, low, high)(candidate);
6189 }
6190 
SetTopLevelContent(const std::string & content_name)6191 void ShipPartMeterValue::SetTopLevelContent(const std::string& content_name) {
6192     if (m_part_name)
6193         m_part_name->SetTopLevelContent(content_name);
6194     if (m_low)
6195         m_low->SetTopLevelContent(content_name);
6196     if (m_high)
6197         m_high->SetTopLevelContent(content_name);
6198 }
6199 
GetCheckSum() const6200 unsigned int ShipPartMeterValue::GetCheckSum() const {
6201     unsigned int retval{0};
6202 
6203     CheckSums::CheckSumCombine(retval, "Condition::ShipPartMeterValue");
6204     CheckSums::CheckSumCombine(retval, m_part_name);
6205     CheckSums::CheckSumCombine(retval, m_meter);
6206     CheckSums::CheckSumCombine(retval, m_low);
6207     CheckSums::CheckSumCombine(retval, m_high);
6208 
6209     TraceLogger() << "GetCheckSum(ShipPartMeterValue): retval: " << retval;
6210     return retval;
6211 }
6212 
6213 ///////////////////////////////////////////////////////////
6214 // EmpireMeterValue                                      //
6215 ///////////////////////////////////////////////////////////
EmpireMeterValue(const std::string & meter,std::unique_ptr<ValueRef::ValueRef<double>> && low,std::unique_ptr<ValueRef::ValueRef<double>> && high)6216 EmpireMeterValue::EmpireMeterValue(const std::string& meter,
6217                                    std::unique_ptr<ValueRef::ValueRef<double>>&& low,
6218                                    std::unique_ptr<ValueRef::ValueRef<double>>&& high) :
6219     EmpireMeterValue(nullptr, meter, std::move(low), std::move(high))
6220 {}
6221 
EmpireMeterValue(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,const std::string & meter,std::unique_ptr<ValueRef::ValueRef<double>> && low,std::unique_ptr<ValueRef::ValueRef<double>> && high)6222 EmpireMeterValue::EmpireMeterValue(std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
6223                                    const std::string& meter,
6224                                    std::unique_ptr<ValueRef::ValueRef<double>>&& low,
6225                                    std::unique_ptr<ValueRef::ValueRef<double>>&& high) :
6226     Condition(),
6227     m_empire_id(std::move(empire_id)),
6228     m_meter(meter),
6229     m_low(std::move(low)),
6230     m_high(std::move(high))
6231 {
6232     auto operands = {m_low.get(), m_high.get()};
6233     m_root_candidate_invariant =
6234         (!m_empire_id || m_empire_id->RootCandidateInvariant()) &&
6235         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
6236     m_target_invariant =
6237         (!m_empire_id || m_empire_id->TargetInvariant()) &&
6238         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
6239     m_source_invariant =
6240         (!m_empire_id || m_empire_id->SourceInvariant()) &&
6241         boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
6242 }
6243 
operator ==(const Condition & rhs) const6244 bool EmpireMeterValue::operator==(const Condition& rhs) const {
6245     if (this == &rhs)
6246         return true;
6247     if (typeid(*this) != typeid(rhs))
6248         return false;
6249 
6250     const EmpireMeterValue& rhs_ = static_cast<const EmpireMeterValue&>(rhs);
6251 
6252     if (m_empire_id != rhs_.m_empire_id)
6253         return false;
6254 
6255     if (m_meter != rhs_.m_meter)
6256         return false;
6257 
6258     CHECK_COND_VREF_MEMBER(m_low)
6259     CHECK_COND_VREF_MEMBER(m_high)
6260 
6261     return true;
6262 }
6263 
6264 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const6265 void EmpireMeterValue::Eval(const ScriptingContext& parent_context,
6266                             ObjectSet& matches, ObjectSet& non_matches,
6267                             SearchDomain search_domain/* = NON_MATCHES*/) const
6268 {
6269     bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) &&
6270                              (!m_low || m_low->LocalCandidateInvariant()) &&
6271                              (!m_high || m_high->LocalCandidateInvariant()) &&
6272                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
6273     if (simple_eval_safe) {
6274         // If m_empire_id is specified (not null), and all parameters are
6275         // local-candidate-invariant, then matching for this condition doesn't
6276         // need to check each candidate object separately for matching, so
6277         // don't need to use EvalImpl and can instead do a simpler transfer
6278         bool match = Match(parent_context);
6279 
6280         // transfer objects to or from candidate set, according to whether the
6281         // specified empire meter was in the requested range
6282         if (match && search_domain == NON_MATCHES) {
6283             // move all objects from non_matches to matches
6284             matches.insert(matches.end(), non_matches.begin(), non_matches.end());
6285             non_matches.clear();
6286         } else if (!match && search_domain == MATCHES) {
6287             // move all objects from matches to non_matches
6288             non_matches.insert(non_matches.end(), matches.begin(), matches.end());
6289             matches.clear();
6290         }
6291 
6292     } else {
6293         // re-evaluate allowed turn range for each candidate object
6294         Condition::Eval(parent_context, matches, non_matches, search_domain);
6295     }
6296 }
6297 
Description(bool negated) const6298 std::string EmpireMeterValue::Description(bool negated/* = false*/) const {
6299     std::string empire_str;
6300     if (m_empire_id) {
6301         int empire_id = ALL_EMPIRES;
6302         if (m_empire_id->ConstantExpr())
6303             empire_id = m_empire_id->Eval();
6304         if (const Empire* empire = GetEmpire(empire_id))
6305             empire_str = empire->Name();
6306         else
6307             empire_str = m_empire_id->Description();
6308     }
6309     std::string low_str = (m_low ? (m_low->ConstantExpr() ?
6310                                     std::to_string(m_low->Eval()) :
6311                                     m_low->Description())
6312                                  : std::to_string(-Meter::LARGE_VALUE));
6313     std::string high_str = (m_high ? (m_high->ConstantExpr() ?
6314                                       std::to_string(m_high->Eval()) :
6315                                       m_high->Description())
6316                                    : std::to_string(Meter::LARGE_VALUE));
6317     return str(FlexibleFormat((!negated)
6318         ? UserString("DESC_EMPIRE_METER_VALUE_CURRENT")
6319         : UserString("DESC_EMPIRE_METER_VALUE_CURRENT_NOT"))
6320                % UserString(m_meter)
6321                % low_str
6322                % high_str
6323                % empire_str);
6324 }
6325 
Dump(unsigned short ntabs) const6326 std::string EmpireMeterValue::Dump(unsigned short ntabs) const {
6327     std::string retval = DumpIndent(ntabs) + "EmpireMeterValue";
6328     if (m_empire_id)
6329         retval += " empire = " + m_empire_id->Dump(ntabs);
6330     retval += " meter = " + m_meter;
6331     if (m_low)
6332         retval += " low = " + m_low->Dump(ntabs);
6333     if (m_high)
6334         retval += " high = " + m_high->Dump(ntabs);
6335     retval += "\n";
6336     return retval;
6337 }
6338 
Match(const ScriptingContext & local_context) const6339 bool EmpireMeterValue::Match(const ScriptingContext& local_context) const {
6340     int empire_id = ALL_EMPIRES;
6341     auto candidate = local_context.condition_local_candidate;
6342     // if m_empire_id not set, default to candidate object's owner
6343     if (!m_empire_id && !candidate) {
6344         ErrorLogger() << "EmpireMeterValue::Match passed no candidate object but expects one due to having no empire id valueref specified and thus wanting to use the local candidate's owner as the empire id";
6345         return false;
6346 
6347     } else if (m_empire_id && !candidate && !m_empire_id->LocalCandidateInvariant()) {
6348         ErrorLogger() << "EmpireMeterValue::Match passed no candidate object but but empire id valueref references the local candidate";
6349         return false;
6350 
6351     } else if (!m_empire_id && candidate) {
6352         // default to candidate's owner if no empire id valueref is specified
6353         empire_id = candidate->Owner();
6354 
6355     } else if (m_empire_id) {
6356         // either candidate exists or m_empire_id is local-candidate-invariant (or both)
6357         empire_id = m_empire_id->Eval(local_context);
6358 
6359     } else {
6360         ErrorLogger() << "EmpireMeterValue::Match reached unexpected default case for candidate and empire id valueref existance";
6361         return false;
6362     }
6363 
6364     const Empire* empire = GetEmpire(empire_id);
6365     if (!empire)
6366         return false;
6367     const Meter* meter = empire->GetMeter(m_meter);
6368     if (!meter)
6369         return false;
6370 
6371     float meter_current = meter->Current();
6372     float low =  (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE);
6373     float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE);
6374 
6375     return (low <= meter_current && meter_current <= high);
6376 }
6377 
SetTopLevelContent(const std::string & content_name)6378 void EmpireMeterValue::SetTopLevelContent(const std::string& content_name) {
6379     if (m_empire_id)
6380         m_empire_id->SetTopLevelContent(content_name);
6381     if (m_low)
6382         m_low->SetTopLevelContent(content_name);
6383     if (m_high)
6384         m_high->SetTopLevelContent(content_name);
6385 }
6386 
GetCheckSum() const6387 unsigned int EmpireMeterValue::GetCheckSum() const {
6388     unsigned int retval{0};
6389 
6390     CheckSums::CheckSumCombine(retval, "Condition::EmpireMeterValue");
6391     CheckSums::CheckSumCombine(retval, m_empire_id);
6392     CheckSums::CheckSumCombine(retval, m_meter);
6393     CheckSums::CheckSumCombine(retval, m_low);
6394     CheckSums::CheckSumCombine(retval, m_high);
6395 
6396     TraceLogger() << "GetCheckSum(EmpireMeterValue): retval: " << retval;
6397     return retval;
6398 }
6399 
6400 ///////////////////////////////////////////////////////////
6401 // EmpireStockpileValue                                  //
6402 ///////////////////////////////////////////////////////////
EmpireStockpileValue(ResourceType stockpile,std::unique_ptr<ValueRef::ValueRef<double>> && low,std::unique_ptr<ValueRef::ValueRef<double>> && high)6403 EmpireStockpileValue::EmpireStockpileValue(ResourceType stockpile,
6404                                            std::unique_ptr<ValueRef::ValueRef<double>>&& low,
6405                                            std::unique_ptr<ValueRef::ValueRef<double>>&& high) :
6406     Condition(),
6407     m_stockpile(stockpile),
6408     m_low(std::move(low)),
6409     m_high(std::move(high))
6410 {
6411     auto operands = {m_low.get(), m_high.get()};
6412     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
6413     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
6414     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
6415 }
6416 
operator ==(const Condition & rhs) const6417 bool EmpireStockpileValue::operator==(const Condition& rhs) const {
6418     if (this == &rhs)
6419         return true;
6420     if (typeid(*this) != typeid(rhs))
6421         return false;
6422 
6423     const EmpireStockpileValue& rhs_ = static_cast<const EmpireStockpileValue&>(rhs);
6424 
6425     if (m_stockpile != rhs_.m_stockpile)
6426         return false;
6427 
6428     CHECK_COND_VREF_MEMBER(m_low)
6429     CHECK_COND_VREF_MEMBER(m_high)
6430 
6431     return true;
6432 }
6433 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const6434 void EmpireStockpileValue::Eval(const ScriptingContext& parent_context,
6435                                 ObjectSet& matches, ObjectSet& non_matches,
6436                                 SearchDomain search_domain/* = NON_MATCHES*/) const
6437 {
6438     // if m_empire_id not set, the local candidate's owner is used, which is not target invariant
6439     bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) &&
6440                              (!m_low || m_low->LocalCandidateInvariant()) &&
6441                              (!m_high || m_high->LocalCandidateInvariant()) &&
6442                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
6443     if (simple_eval_safe) {
6444         // If m_empire_id is specified (not null), and all parameters are
6445         // local-candidate-invariant, then matching for this condition doesn't
6446         // need to check each candidate object separately for matching, so
6447         // don't need to use EvalImpl and can instead do a simpler transfer
6448         bool match = Match(parent_context);
6449 
6450         // transfer objects to or from candidate set, according to whether the
6451         // specified empire meter was in the requested range
6452         if (match && search_domain == NON_MATCHES) {
6453             // move all objects from non_matches to matches
6454             matches.insert(matches.end(), non_matches.begin(), non_matches.end());
6455             non_matches.clear();
6456         } else if (!match && search_domain == MATCHES) {
6457             // move all objects from matches to non_matches
6458             non_matches.insert(non_matches.end(), matches.begin(), matches.end());
6459             matches.clear();
6460         }
6461 
6462     } else {
6463         // re-evaluate all parameters for each candidate object.
6464         // could optimize further by only re-evaluating the local-candidate
6465         // variants.
6466         Condition::Eval(parent_context, matches, non_matches, search_domain);
6467     }
6468 }
6469 
Description(bool negated) const6470 std::string EmpireStockpileValue::Description(bool negated/* = false*/) const {
6471     std::string low_str = m_low->ConstantExpr() ?
6472                             std::to_string(m_low->Eval()) :
6473                             m_low->Description();
6474     std::string high_str = m_high->ConstantExpr() ?
6475                             std::to_string(m_high->Eval()) :
6476                             m_high->Description();
6477     return str(FlexibleFormat((!negated)
6478         ? UserString("DESC_EMPIRE_STOCKPILE_VALUE")
6479         : UserString("DESC_EMPIRE_STOCKPILE_VALUE_NOT"))
6480                % UserString(boost::lexical_cast<std::string>(m_stockpile))
6481                % low_str
6482                % high_str);
6483 }
6484 
Dump(unsigned short ntabs) const6485 std::string EmpireStockpileValue::Dump(unsigned short ntabs) const {
6486     std::string retval = DumpIndent(ntabs);
6487     switch (m_stockpile) {
6488     case RE_TRADE:      retval += "OwnerTradeStockpile";    break;
6489     case RE_RESEARCH:   retval += "OwnerResearchStockpile"; break;
6490     case RE_INDUSTRY:   retval += "OwnerIndustryStockpile"; break;
6491     default:            retval += "?";                      break;
6492     }
6493     if (m_empire_id)
6494         retval += " empire = " + m_empire_id->Dump(ntabs);
6495     if (m_low)
6496         retval += " low = " + m_low->Dump(ntabs);
6497     if (m_high)
6498         retval += " high = " + m_high->Dump(ntabs);
6499     retval += "\n";
6500     return retval;
6501 }
6502 
Match(const ScriptingContext & local_context) const6503 bool EmpireStockpileValue::Match(const ScriptingContext& local_context) const {
6504     int empire_id = ALL_EMPIRES;
6505     auto candidate = local_context.condition_local_candidate;
6506     // if m_empire_id not set, default to candidate object's owner
6507     if (!m_empire_id && !candidate) {
6508         ErrorLogger() << "EmpireStockpileValue::Match passed no candidate object but expects one due to having no empire id valueref specified and thus wanting to use the local candidate's owner as the empire id";
6509         return false;
6510 
6511     } else if (m_empire_id && !candidate && !m_empire_id->LocalCandidateInvariant()) {
6512         ErrorLogger() << "EmpireStockpileValue::Match passed no candidate object but but empire id valueref references the local candidate";
6513         return false;
6514 
6515     } else if (!m_empire_id && candidate) {
6516         // default to candidate's owner if no empire id valueref is specified
6517         empire_id = candidate->Owner();
6518 
6519     } else if (m_empire_id) {
6520         // either candidate exists or m_empire_id is local-candidate-invariant (or both)
6521         empire_id = m_empire_id->Eval(local_context);
6522 
6523     } else {
6524         ErrorLogger() << "EmpireStockpileValue::Match reached unexpected default case for candidate and empire id valueref existance";
6525         return false;
6526     }
6527 
6528     const Empire* empire = GetEmpire(empire_id);
6529     if (!empire)
6530          return false;
6531 
6532     try {
6533         float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE);
6534         float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE);
6535         float amount = empire->ResourceStockpile(m_stockpile);
6536         return (low <= amount && amount <= high);
6537     } catch (...) {
6538         return false;
6539     }
6540 }
6541 
SetTopLevelContent(const std::string & content_name)6542 void EmpireStockpileValue::SetTopLevelContent(const std::string& content_name) {
6543     if (m_empire_id)
6544         m_empire_id->SetTopLevelContent(content_name);
6545     if (m_low)
6546         m_low->SetTopLevelContent(content_name);
6547     if (m_high)
6548         m_high->SetTopLevelContent(content_name);
6549 }
6550 
GetCheckSum() const6551 unsigned int EmpireStockpileValue::GetCheckSum() const {
6552     unsigned int retval{0};
6553 
6554     CheckSums::CheckSumCombine(retval, "Condition::EmpireStockpileValue");
6555     CheckSums::CheckSumCombine(retval, m_empire_id);
6556     CheckSums::CheckSumCombine(retval, m_stockpile);
6557     CheckSums::CheckSumCombine(retval, m_low);
6558     CheckSums::CheckSumCombine(retval, m_high);
6559 
6560     TraceLogger() << "GetCheckSum(EmpireStockpileValue): retval: " << retval;
6561     return retval;
6562 }
6563 
6564 ///////////////////////////////////////////////////////////
6565 // OwnerHasTech                                          //
6566 ///////////////////////////////////////////////////////////
OwnerHasTech(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,std::unique_ptr<ValueRef::ValueRef<std::string>> && name)6567 OwnerHasTech::OwnerHasTech(std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
6568                            std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
6569     Condition(),
6570     m_name(std::move(name)),
6571     m_empire_id(std::move(empire_id))
6572 {
6573     m_root_candidate_invariant =
6574         (!m_empire_id || m_empire_id->RootCandidateInvariant()) &&
6575         (!m_name || m_name->RootCandidateInvariant());
6576     m_target_invariant =
6577         (!m_empire_id || m_empire_id->TargetInvariant()) &&
6578         (!m_name || m_name->TargetInvariant());
6579     m_source_invariant =
6580         (!m_empire_id || m_empire_id->SourceInvariant()) &&
6581         (!m_name || m_name->SourceInvariant());
6582 }
6583 
OwnerHasTech(std::unique_ptr<ValueRef::ValueRef<std::string>> && name)6584 OwnerHasTech::OwnerHasTech(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
6585     OwnerHasTech(nullptr, std::move(name))
6586 {}
6587 
operator ==(const Condition & rhs) const6588 bool OwnerHasTech::operator==(const Condition& rhs) const {
6589     if (this == &rhs)
6590         return true;
6591     if (typeid(*this) != typeid(rhs))
6592         return false;
6593 
6594     const OwnerHasTech& rhs_ = static_cast<const OwnerHasTech&>(rhs);
6595 
6596     if (m_empire_id != rhs_.m_empire_id)
6597         return false;
6598 
6599     CHECK_COND_VREF_MEMBER(m_name)
6600 
6601     return true;
6602 }
6603 
6604 namespace {
6605     struct OwnerHasTechSimpleMatch {
OwnerHasTechSimpleMatchCondition::__anone9a56faf5e11::OwnerHasTechSimpleMatch6606         OwnerHasTechSimpleMatch(int empire_id, const std::string& name) :
6607             m_empire_id(empire_id),
6608             m_name(name)
6609         {}
6610 
operator ()Condition::__anone9a56faf5e11::OwnerHasTechSimpleMatch6611         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
6612             if (!candidate)
6613                 return false;
6614 
6615             int actual_empire_id = m_empire_id;
6616             if (m_empire_id == ALL_EMPIRES) {
6617                 if (candidate->Unowned())
6618                     return false;
6619                 actual_empire_id = candidate->Owner();
6620             }
6621 
6622             const Empire* empire = GetEmpire(actual_empire_id);
6623             if (!empire)
6624                 return false;
6625 
6626             return empire->TechResearched(m_name);
6627         }
6628 
6629         int         m_empire_id;
6630         std::string m_name;
6631     };
6632 }
6633 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const6634 void OwnerHasTech::Eval(const ScriptingContext& parent_context,
6635                         ObjectSet& matches, ObjectSet& non_matches,
6636                         SearchDomain search_domain/* = NON_MATCHES*/) const
6637 {
6638     // if m_empire_id not set, the local candidate's owner is used, which is not target invariant
6639     bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) &&
6640                              (!m_name || m_name->LocalCandidateInvariant()) &&
6641                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
6642     if (simple_eval_safe) {
6643         // evaluate number limits once, use to match all candidates
6644         int empire_id = m_empire_id->Eval(parent_context);   // check above should ensure m_empire_id is non-null
6645         std::string name = m_name ? m_name->Eval(parent_context) : "";
6646         EvalImpl(matches, non_matches, search_domain, OwnerHasTechSimpleMatch(empire_id, name));
6647     } else {
6648         // re-evaluate allowed turn range for each candidate object
6649         Condition::Eval(parent_context, matches, non_matches, search_domain);
6650     }
6651 }
6652 
Description(bool negated) const6653 std::string OwnerHasTech::Description(bool negated/* = false*/) const {
6654     std::string name_str;
6655     if (m_name) {
6656         name_str = m_name->Description();
6657         if (m_name->ConstantExpr() && UserStringExists(name_str))
6658             name_str = UserString(name_str);
6659     }
6660     return str(FlexibleFormat((!negated)
6661         ? UserString("DESC_OWNER_HAS_TECH")
6662         : UserString("DESC_OWNER_HAS_TECH_NOT"))
6663         % name_str);
6664 }
6665 
Dump(unsigned short ntabs) const6666 std::string OwnerHasTech::Dump(unsigned short ntabs) const {
6667     std::string retval = DumpIndent(ntabs) + "OwnerHasTech";
6668     if (m_empire_id)
6669         retval += " empire = " + m_empire_id->Dump(ntabs);
6670     if (m_name)
6671         retval += " name = " + m_name->Dump(ntabs);
6672     retval += "\n";
6673     return retval;
6674 }
6675 
Match(const ScriptingContext & local_context) const6676 bool OwnerHasTech::Match(const ScriptingContext& local_context) const {
6677     auto candidate = local_context.condition_local_candidate;
6678     if (!candidate) {
6679         ErrorLogger() << "OwnerHasTech::Match passed no candidate object";
6680         return false;
6681     }
6682 
6683     int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner());
6684     if (empire_id == ALL_EMPIRES)
6685         return false;
6686     std::string name = m_name ? m_name->Eval(local_context) : "";
6687 
6688     return OwnerHasTechSimpleMatch(empire_id, name)(candidate);
6689 }
6690 
SetTopLevelContent(const std::string & content_name)6691 void OwnerHasTech::SetTopLevelContent(const std::string& content_name) {
6692     if (m_empire_id)
6693         m_empire_id->SetTopLevelContent(content_name);
6694     if (m_name)
6695         m_name->SetTopLevelContent(content_name);
6696 }
6697 
GetCheckSum() const6698 unsigned int OwnerHasTech::GetCheckSum() const {
6699     unsigned int retval{0};
6700 
6701     CheckSums::CheckSumCombine(retval, "Condition::OwnerHasTech");
6702     CheckSums::CheckSumCombine(retval, m_empire_id);
6703     CheckSums::CheckSumCombine(retval, m_name);
6704 
6705     TraceLogger() << "GetCheckSum(OwnerHasTech): retval: " << retval;
6706     return retval;
6707 }
6708 
6709 ///////////////////////////////////////////////////////////
6710 // OwnerHasBuildingTypeAvailable                         //
6711 ///////////////////////////////////////////////////////////
OwnerHasBuildingTypeAvailable(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,std::unique_ptr<ValueRef::ValueRef<std::string>> && name)6712 OwnerHasBuildingTypeAvailable::OwnerHasBuildingTypeAvailable(
6713     std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
6714     std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
6715     Condition(),
6716     m_name(std::move(name)),
6717     m_empire_id(std::move(empire_id))
6718 {
6719     m_root_candidate_invariant =
6720         (!m_empire_id || m_empire_id->RootCandidateInvariant()) &&
6721         (!m_name || m_name->RootCandidateInvariant());
6722     m_target_invariant =
6723         (!m_empire_id || m_empire_id->TargetInvariant()) &&
6724         (!m_name || m_name->TargetInvariant());
6725     m_source_invariant =
6726         (!m_empire_id || m_empire_id->SourceInvariant()) &&
6727         (!m_name || m_name->SourceInvariant());
6728 }
6729 
OwnerHasBuildingTypeAvailable(const std::string & name)6730 OwnerHasBuildingTypeAvailable::OwnerHasBuildingTypeAvailable(const std::string& name) :
6731     OwnerHasBuildingTypeAvailable(nullptr, std::make_unique<ValueRef::Constant<std::string>>(name))
6732 {}
6733 
OwnerHasBuildingTypeAvailable(std::unique_ptr<ValueRef::ValueRef<std::string>> && name)6734 OwnerHasBuildingTypeAvailable::OwnerHasBuildingTypeAvailable(
6735     std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
6736     OwnerHasBuildingTypeAvailable(nullptr, std::move(name))
6737 {}
6738 
operator ==(const Condition & rhs) const6739 bool OwnerHasBuildingTypeAvailable::operator==(const Condition& rhs) const {
6740     if (this == &rhs)
6741         return true;
6742     if (typeid(*this) != typeid(rhs))
6743         return false;
6744 
6745     const OwnerHasBuildingTypeAvailable& rhs_ = static_cast<const OwnerHasBuildingTypeAvailable&>(rhs);
6746 
6747     if (m_empire_id != rhs_.m_empire_id)
6748         return false;
6749 
6750     CHECK_COND_VREF_MEMBER(m_name)
6751 
6752     return true;
6753 }
6754 
6755 namespace {
6756     struct OwnerHasBuildingTypeAvailableSimpleMatch {
OwnerHasBuildingTypeAvailableSimpleMatchCondition::__anone9a56faf5f11::OwnerHasBuildingTypeAvailableSimpleMatch6757         OwnerHasBuildingTypeAvailableSimpleMatch(int empire_id, const std::string& name) :
6758             m_empire_id(empire_id),
6759             m_name(name)
6760         {}
6761 
operator ()Condition::__anone9a56faf5f11::OwnerHasBuildingTypeAvailableSimpleMatch6762         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
6763             if (!candidate)
6764                 return false;
6765 
6766             int actual_empire_id = m_empire_id;
6767             if (m_empire_id == ALL_EMPIRES) {
6768                 if (candidate->Unowned())
6769                     return false;
6770                 actual_empire_id = candidate->Owner();
6771             }
6772 
6773             const Empire* empire = GetEmpire(actual_empire_id);
6774             if (!empire)
6775                 return false;
6776 
6777             return empire->BuildingTypeAvailable(m_name);
6778         }
6779 
6780         int         m_empire_id;
6781         std::string m_name;
6782     };
6783 }
6784 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const6785 void OwnerHasBuildingTypeAvailable::Eval(const ScriptingContext& parent_context,
6786                                          ObjectSet& matches, ObjectSet& non_matches,
6787                                          SearchDomain search_domain/* = NON_MATCHES*/) const
6788 {
6789     // if m_empire_id not set, the local candidate's owner is used, which is not target invariant
6790     bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) &&
6791                              (!m_name || m_name->LocalCandidateInvariant()) &&
6792                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
6793     if (simple_eval_safe) {
6794         // evaluate number limits once, use to match all candidates
6795         int empire_id = m_empire_id->Eval(parent_context);   // check above should ensure m_empire_id is non-null
6796         std::string name = m_name ? m_name->Eval(parent_context) : "";
6797         EvalImpl(matches, non_matches, search_domain, OwnerHasBuildingTypeAvailableSimpleMatch(empire_id, name));
6798     } else {
6799         // re-evaluate allowed turn range for each candidate object
6800         Condition::Eval(parent_context, matches, non_matches, search_domain);
6801     }
6802 }
6803 
Description(bool negated) const6804 std::string OwnerHasBuildingTypeAvailable::Description(bool negated/* = false*/) const {
6805     // used internally for a tooltip where context is apparent, so don't need
6806     // to name builing type here
6807     return (!negated)
6808         ? UserString("DESC_OWNER_HAS_BUILDING_TYPE")
6809         : UserString("DESC_OWNER_HAS_BUILDING_TYPE_NOT");
6810 }
6811 
Dump(unsigned short ntabs) const6812 std::string OwnerHasBuildingTypeAvailable::Dump(unsigned short ntabs) const {
6813     std::string retval= DumpIndent(ntabs) + "OwnerHasBuildingTypeAvailable";
6814     if (m_empire_id)
6815         retval += " empire = " + m_empire_id->Dump(ntabs);
6816     if (m_name)
6817         retval += " name = " + m_name->Dump(ntabs);
6818     retval += "\n";
6819     return retval;
6820 }
6821 
Match(const ScriptingContext & local_context) const6822 bool OwnerHasBuildingTypeAvailable::Match(const ScriptingContext& local_context) const {
6823     auto candidate = local_context.condition_local_candidate;
6824     if (!candidate) {
6825         ErrorLogger() << "OwnerHasTech::Match passed no candidate object";
6826         return false;
6827     }
6828 
6829     int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner());
6830     if (empire_id == ALL_EMPIRES)
6831         return false;
6832     std::string name = m_name ? m_name->Eval(local_context) : "";
6833 
6834     return OwnerHasBuildingTypeAvailableSimpleMatch(empire_id, name)(candidate);
6835 }
6836 
SetTopLevelContent(const std::string & content_name)6837 void OwnerHasBuildingTypeAvailable::SetTopLevelContent(const std::string& content_name) {
6838     if (m_empire_id)
6839         m_empire_id->SetTopLevelContent(content_name);
6840     if (m_name)
6841         m_name->SetTopLevelContent(content_name);
6842 }
6843 
GetCheckSum() const6844 unsigned int OwnerHasBuildingTypeAvailable::GetCheckSum() const {
6845     unsigned int retval{0};
6846 
6847     CheckSums::CheckSumCombine(retval, "Condition::OwnerHasBuildingTypeAvailable");
6848     CheckSums::CheckSumCombine(retval, m_empire_id);
6849     CheckSums::CheckSumCombine(retval, m_name);
6850 
6851     TraceLogger() << "GetCheckSum(OwnerHasBuildingTypeAvailable): retval: " << retval;
6852     return retval;
6853 }
6854 
6855 ///////////////////////////////////////////////////////////
6856 // OwnerHasShipDesignAvailable                           //
6857 ///////////////////////////////////////////////////////////
OwnerHasShipDesignAvailable(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,std::unique_ptr<ValueRef::ValueRef<int>> && design_id)6858 OwnerHasShipDesignAvailable::OwnerHasShipDesignAvailable(
6859     std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
6860     std::unique_ptr<ValueRef::ValueRef<int>>&& design_id) :
6861     Condition(),
6862     m_id(std::move(design_id)),
6863     m_empire_id(std::move(empire_id))
6864 {
6865     auto operands = {m_id.get(), m_empire_id.get()};
6866     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
6867     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
6868     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
6869 }
6870 
OwnerHasShipDesignAvailable(int design_id)6871 OwnerHasShipDesignAvailable::OwnerHasShipDesignAvailable(int design_id) :
6872     OwnerHasShipDesignAvailable(nullptr, std::make_unique<ValueRef::Constant<int>>(design_id))
6873 {}
6874 
OwnerHasShipDesignAvailable(std::unique_ptr<ValueRef::ValueRef<int>> && design_id)6875 OwnerHasShipDesignAvailable::OwnerHasShipDesignAvailable(std::unique_ptr<ValueRef::ValueRef<int>>&& design_id) :
6876     OwnerHasShipDesignAvailable(nullptr, std::move(design_id))
6877 {}
6878 
operator ==(const Condition & rhs) const6879 bool OwnerHasShipDesignAvailable::operator==(const Condition& rhs) const {
6880     if (this == &rhs)
6881         return true;
6882     if (typeid(*this) != typeid(rhs))
6883         return false;
6884 
6885     const OwnerHasShipDesignAvailable& rhs_ = static_cast<const OwnerHasShipDesignAvailable&>(rhs);
6886 
6887     if (m_empire_id != rhs_.m_empire_id)
6888         return false;
6889 
6890     CHECK_COND_VREF_MEMBER(m_id)
6891 
6892     return true;
6893 }
6894 
6895 namespace {
6896     struct OwnerHasShipDesignAvailableSimpleMatch {
OwnerHasShipDesignAvailableSimpleMatchCondition::__anone9a56faf6311::OwnerHasShipDesignAvailableSimpleMatch6897         OwnerHasShipDesignAvailableSimpleMatch(int empire_id, int design_id) :
6898             m_empire_id(empire_id),
6899             m_id(design_id)
6900         {}
6901 
operator ()Condition::__anone9a56faf6311::OwnerHasShipDesignAvailableSimpleMatch6902         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
6903             if (!candidate)
6904                 return false;
6905 
6906             int actual_empire_id = m_empire_id;
6907             if (m_empire_id == ALL_EMPIRES) {
6908                 if (candidate->Unowned())
6909                     return false;
6910                 actual_empire_id = candidate->Owner();
6911             }
6912 
6913             const Empire* empire = GetEmpire(actual_empire_id);
6914             if (!empire)
6915                 return false;
6916 
6917             return empire->ShipDesignAvailable(m_id);
6918         }
6919 
6920         int m_empire_id;
6921         int m_id;
6922     };
6923 }
6924 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const6925 void OwnerHasShipDesignAvailable::Eval(const ScriptingContext& parent_context,
6926                                        ObjectSet& matches, ObjectSet& non_matches,
6927                                        SearchDomain search_domain/* = NON_MATCHES*/) const
6928 {
6929     // if m_empire_id not set, the local candidate's owner is used, which is not target invariant
6930     bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) &&
6931                              (!m_id || m_id->LocalCandidateInvariant()) &&
6932                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
6933     if (simple_eval_safe) {
6934         // evaluate number limits once, use to match all candidates
6935         int empire_id = m_empire_id->Eval(parent_context);   // check above should ensure m_empire_id is non-null
6936         int design_id = m_id ? m_id->Eval(parent_context) : INVALID_DESIGN_ID;
6937         EvalImpl(matches, non_matches, search_domain, OwnerHasShipDesignAvailableSimpleMatch(empire_id, design_id));
6938     } else {
6939         // re-evaluate allowed turn range for each candidate object
6940         Condition::Eval(parent_context, matches, non_matches, search_domain);
6941     }
6942 }
6943 
Description(bool negated) const6944 std::string OwnerHasShipDesignAvailable::Description(bool negated/* = false*/) const {
6945     // used internally for a tooltip where context is apparent, so don't need
6946     // to specify design here
6947     return (!negated)
6948         ? UserString("DESC_OWNER_HAS_SHIP_DESIGN")
6949         : UserString("DESC_OWNER_HAS_SHIP_DESIGN_NOT");
6950 }
6951 
Dump(unsigned short ntabs) const6952 std::string OwnerHasShipDesignAvailable::Dump(unsigned short ntabs) const {
6953     std::string retval = DumpIndent(ntabs) + "OwnerHasShipDesignAvailable";
6954     if (m_empire_id)
6955         retval += " empire = " + m_empire_id->Dump(ntabs);
6956     if (m_id)
6957         retval += " id = " + m_id->Dump(ntabs);
6958     retval += "\n";
6959     return retval;
6960 }
6961 
Match(const ScriptingContext & local_context) const6962 bool OwnerHasShipDesignAvailable::Match(const ScriptingContext& local_context) const {
6963     auto candidate = local_context.condition_local_candidate;
6964     if (!candidate) {
6965         ErrorLogger() << "OwnerHasTech::Match passed no candidate object";
6966         return false;
6967     }
6968 
6969     int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner());
6970     if (empire_id == ALL_EMPIRES)
6971         return false;
6972     int design_id = m_id ? m_id->Eval(local_context) : INVALID_DESIGN_ID;
6973 
6974     return OwnerHasShipDesignAvailableSimpleMatch(empire_id, design_id)(candidate);
6975 }
6976 
SetTopLevelContent(const std::string & content_name)6977 void OwnerHasShipDesignAvailable::SetTopLevelContent(const std::string& content_name) {
6978     if (m_empire_id)
6979         m_empire_id->SetTopLevelContent(content_name);
6980     if (m_id)
6981         m_id->SetTopLevelContent(content_name);
6982 }
6983 
GetCheckSum() const6984 unsigned int OwnerHasShipDesignAvailable::GetCheckSum() const {
6985     unsigned int retval{0};
6986 
6987     CheckSums::CheckSumCombine(retval, "Condition::OwnerHasShipDesignAvailable");
6988     CheckSums::CheckSumCombine(retval, m_empire_id);
6989     CheckSums::CheckSumCombine(retval, m_id);
6990 
6991     TraceLogger() << "GetCheckSum(OwnerHasShipDesignAvailable): retval: " << retval;
6992     return retval;
6993 }
6994 
6995 ///////////////////////////////////////////////////////////
6996 // OwnerHasShipPartAvailable                             //
6997 ///////////////////////////////////////////////////////////
OwnerHasShipPartAvailable(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,std::unique_ptr<ValueRef::ValueRef<std::string>> && name)6998 OwnerHasShipPartAvailable::OwnerHasShipPartAvailable(
6999     std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
7000     std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
7001     Condition(),
7002     m_name(std::move(name)),
7003     m_empire_id(std::move(empire_id))
7004 {
7005     m_root_candidate_invariant =
7006         (!m_empire_id || m_empire_id->RootCandidateInvariant()) &&
7007         (!m_name || m_name->RootCandidateInvariant());
7008     m_target_invariant =
7009         (!m_empire_id || m_empire_id->TargetInvariant()) &&
7010         (!m_name || m_name->TargetInvariant());
7011     m_source_invariant =
7012         (!m_empire_id || m_empire_id->TargetInvariant()) &&
7013         (!m_name || m_name->TargetInvariant());
7014 }
7015 
OwnerHasShipPartAvailable(const std::string & name)7016 OwnerHasShipPartAvailable::OwnerHasShipPartAvailable(const std::string& name) :
7017     OwnerHasShipPartAvailable(nullptr, std::make_unique<ValueRef::Constant<std::string>>(name))
7018 {}
7019 
OwnerHasShipPartAvailable(std::unique_ptr<ValueRef::ValueRef<std::string>> && name)7020 OwnerHasShipPartAvailable::OwnerHasShipPartAvailable(std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
7021     OwnerHasShipPartAvailable(nullptr, std::move(name))
7022 {}
7023 
operator ==(const Condition & rhs) const7024 bool OwnerHasShipPartAvailable::operator==(const Condition& rhs) const {
7025     if (this == &rhs)
7026         return true;
7027     if (typeid(*this) != typeid(rhs))
7028         return false;
7029 
7030     const OwnerHasShipPartAvailable& rhs_ =
7031         static_cast<const OwnerHasShipPartAvailable&>(rhs);
7032 
7033     if (m_empire_id != rhs_.m_empire_id)
7034         return false;
7035 
7036     CHECK_COND_VREF_MEMBER(m_name)
7037 
7038     return true;
7039 }
7040 
7041 namespace {
7042     struct OwnerHasShipPartAvailableSimpleMatch {
OwnerHasShipPartAvailableSimpleMatchCondition::__anone9a56faf6411::OwnerHasShipPartAvailableSimpleMatch7043         OwnerHasShipPartAvailableSimpleMatch(int empire_id, const std::string& name) :
7044             m_empire_id(empire_id),
7045             m_name(name)
7046         {}
7047 
operator ()Condition::__anone9a56faf6411::OwnerHasShipPartAvailableSimpleMatch7048         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
7049             if (!candidate)
7050                 return false;
7051 
7052             int actual_empire_id = m_empire_id;
7053             if (m_empire_id == ALL_EMPIRES) {
7054                 if (candidate->Unowned())
7055                     return false;
7056                 actual_empire_id = candidate->Owner();
7057             }
7058 
7059             const Empire* empire = GetEmpire(actual_empire_id);
7060             if (!empire)
7061                 return false;
7062 
7063             return empire->ShipPartAvailable(m_name);
7064         }
7065 
7066         int         m_empire_id;
7067         std::string m_name;
7068     };
7069 }
7070 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const7071 void OwnerHasShipPartAvailable::Eval(const ScriptingContext& parent_context,
7072                                      ObjectSet& matches, ObjectSet& non_matches,
7073                                      SearchDomain search_domain/* = NON_MATCHES*/) const
7074 {
7075     // if m_empire_id not set, the local candidate's owner is used, which is not target invariant
7076     bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) &&
7077                              (!m_name || m_name->LocalCandidateInvariant()) &&
7078                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
7079     if (simple_eval_safe) {
7080         // evaluate number limits once, use to match all candidates
7081         int empire_id = m_empire_id->Eval(parent_context);   // check above should ensure m_empire_id is non-null
7082         std::string name = m_name ? m_name->Eval(parent_context) : "";
7083         EvalImpl(matches, non_matches, search_domain, OwnerHasShipPartAvailableSimpleMatch(empire_id, name));
7084     } else {
7085         // re-evaluate allowed turn range for each candidate object
7086         Condition::Eval(parent_context, matches, non_matches, search_domain);
7087     }
7088 }
7089 
Description(bool negated) const7090 std::string OwnerHasShipPartAvailable::Description(bool negated/* = false*/) const {
7091     return (!negated)
7092         ? UserString("DESC_OWNER_HAS_SHIP_PART")
7093         : UserString("DESC_OWNER_HAS_SHIP_PART_NOT");
7094 }
7095 
Dump(unsigned short ntabs) const7096 std::string OwnerHasShipPartAvailable::Dump(unsigned short ntabs) const {
7097     std::string retval = DumpIndent(ntabs) + "OwnerHasShipPartAvailable";
7098     if (m_empire_id)
7099         retval += " empire = " + m_empire_id->Dump(ntabs);
7100     if (m_name)
7101         retval += " name = " + m_name->Dump(ntabs);
7102     retval += "\n";
7103     return retval;
7104 }
7105 
Match(const ScriptingContext & local_context) const7106 bool OwnerHasShipPartAvailable::Match(const ScriptingContext& local_context) const {
7107     auto candidate = local_context.condition_local_candidate;
7108     if (!candidate) {
7109         ErrorLogger() << "OwnerHasShipPart::Match passed no candidate object";
7110         return false;
7111     }
7112 
7113     int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner());
7114     if (empire_id == ALL_EMPIRES)
7115         return false;
7116     std::string name = m_name ? m_name->Eval(local_context) : "";
7117 
7118     return OwnerHasShipPartAvailableSimpleMatch(empire_id, name)(candidate);
7119 }
7120 
SetTopLevelContent(const std::string & content_name)7121 void OwnerHasShipPartAvailable::SetTopLevelContent(const std::string& content_name) {
7122     if (m_empire_id)
7123         m_empire_id->SetTopLevelContent(content_name);
7124     if (m_name)
7125         m_name->SetTopLevelContent(content_name);
7126 }
7127 
GetCheckSum() const7128 unsigned int OwnerHasShipPartAvailable::GetCheckSum() const {
7129     unsigned int retval{0};
7130 
7131     CheckSums::CheckSumCombine(retval, "Condition::OwnerHasShipPartAvailable");
7132     CheckSums::CheckSumCombine(retval, m_empire_id);
7133     CheckSums::CheckSumCombine(retval, m_name);
7134 
7135     TraceLogger() << "GetCheckSum(OwnerHasShipPartAvailable): retval: " << retval;
7136     return retval;
7137 }
7138 
7139 ///////////////////////////////////////////////////////////
7140 // VisibleToEmpire                                       //
7141 ///////////////////////////////////////////////////////////
VisibleToEmpire(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id)7142 VisibleToEmpire::VisibleToEmpire(std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id) :
7143     Condition(),
7144     m_empire_id(std::move(empire_id))
7145 {
7146     m_root_candidate_invariant = !m_empire_id || m_empire_id->RootCandidateInvariant();
7147     m_target_invariant = !m_empire_id || m_empire_id->TargetInvariant();
7148     m_source_invariant = !m_empire_id || m_empire_id->SourceInvariant();
7149 }
7150 
operator ==(const Condition & rhs) const7151 bool VisibleToEmpire::operator==(const Condition& rhs) const {
7152     if (this == &rhs)
7153         return true;
7154     if (typeid(*this) != typeid(rhs))
7155         return false;
7156 
7157     const VisibleToEmpire& rhs_ = static_cast<const VisibleToEmpire&>(rhs);
7158 
7159     CHECK_COND_VREF_MEMBER(m_empire_id)
7160 
7161     return true;
7162 }
7163 
7164 namespace {
7165     struct VisibleToEmpireSimpleMatch {
VisibleToEmpireSimpleMatchCondition::__anone9a56faf6511::VisibleToEmpireSimpleMatch7166         VisibleToEmpireSimpleMatch(int empire_id,
7167                                    const Universe::EmpireObjectVisibilityMap& vis_map) :
7168             m_empire_id(empire_id),
7169             vis_map(vis_map)
7170         {}
7171 
operator ()Condition::__anone9a56faf6511::VisibleToEmpireSimpleMatch7172         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
7173             if (!candidate)
7174                 return false;
7175 
7176             // if override is empty, use universe state
7177             if (vis_map.empty())
7178                 return candidate->GetVisibility(m_empire_id) > VIS_NO_VISIBILITY;
7179 
7180             // if override specified, get visibility info from it
7181             auto empire_it = vis_map.find(m_empire_id);
7182             if (empire_it == vis_map.end())
7183                 return false;
7184             const auto& object_map = empire_it->second;
7185             auto object_it = object_map.find(candidate->ID());
7186             if (object_it == object_map.end())
7187                 return false;
7188             return object_it->second > VIS_NO_VISIBILITY;
7189         }
7190 
7191         int m_empire_id;
7192         const Universe::EmpireObjectVisibilityMap& vis_map;
7193     };
7194 }
7195 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const7196 void VisibleToEmpire::Eval(const ScriptingContext& parent_context,
7197                            ObjectSet& matches, ObjectSet& non_matches,
7198                            SearchDomain search_domain/* = NON_MATCHES*/) const
7199 {
7200     bool simple_eval_safe = m_empire_id->ConstantExpr() ||
7201                             (m_empire_id->LocalCandidateInvariant() &&
7202                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
7203     if (simple_eval_safe) {
7204         // evaluate empire id once, and use to check all candidate objects
7205         int empire_id = m_empire_id->Eval(parent_context);
7206 
7207         // need to check visibility of each candidate object separately
7208         EvalImpl(matches, non_matches, search_domain,
7209                  VisibleToEmpireSimpleMatch(empire_id, parent_context.combat_info.empire_object_visibility));
7210     } else {
7211         // re-evaluate empire id for each candidate object
7212         Condition::Eval(parent_context, matches, non_matches, search_domain);
7213     }
7214 }
7215 
Description(bool negated) const7216 std::string VisibleToEmpire::Description(bool negated/* = false*/) const {
7217     std::string empire_str;
7218     if (m_empire_id) {
7219         int empire_id = ALL_EMPIRES;
7220         if (m_empire_id->ConstantExpr())
7221             empire_id = m_empire_id->Eval();
7222         if (const Empire* empire = GetEmpire(empire_id))
7223             empire_str = empire->Name();
7224         else
7225             empire_str = m_empire_id->Description();
7226     }
7227 
7228     return str(FlexibleFormat((!negated)
7229         ? UserString("DESC_VISIBLE_TO_EMPIRE")
7230         : UserString("DESC_VISIBLE_TO_EMPIRE_NOT"))
7231                % empire_str);
7232 }
7233 
Dump(unsigned short ntabs) const7234 std::string VisibleToEmpire::Dump(unsigned short ntabs) const {
7235     std::string retval = DumpIndent(ntabs) + "VisibleToEmpire";
7236     if (m_empire_id)
7237         retval += " empire = " + m_empire_id->Dump(ntabs);
7238     retval += "\n";
7239     return retval;
7240 }
7241 
Match(const ScriptingContext & local_context) const7242 bool VisibleToEmpire::Match(const ScriptingContext& local_context) const {
7243     auto candidate = local_context.condition_local_candidate;
7244     if (!candidate) {
7245         ErrorLogger() << "VisibleToEmpire::Match passed no candidate object";
7246         return false;
7247     }
7248 
7249     int empire_id = m_empire_id->Eval(local_context);
7250     return VisibleToEmpireSimpleMatch(empire_id, local_context.combat_info.empire_object_visibility)(candidate);
7251 }
7252 
SetTopLevelContent(const std::string & content_name)7253 void VisibleToEmpire::SetTopLevelContent(const std::string& content_name) {
7254     if (m_empire_id)
7255         m_empire_id->SetTopLevelContent(content_name);
7256 }
7257 
GetCheckSum() const7258 unsigned int VisibleToEmpire::GetCheckSum() const {
7259     unsigned int retval{0};
7260 
7261     CheckSums::CheckSumCombine(retval, "Condition::VisibleToEmpire");
7262     CheckSums::CheckSumCombine(retval, m_empire_id);
7263 
7264     TraceLogger() << "GetCheckSum(VisibleToEmpire): retval: " << retval;
7265     return retval;
7266 }
7267 
7268 ///////////////////////////////////////////////////////////
7269 // WithinDistance                                        //
7270 ///////////////////////////////////////////////////////////
WithinDistance(std::unique_ptr<ValueRef::ValueRef<double>> && distance,std::unique_ptr<Condition> && condition)7271 WithinDistance::WithinDistance(std::unique_ptr<ValueRef::ValueRef<double>>&& distance,
7272                                std::unique_ptr<Condition>&& condition) :
7273     Condition(),
7274     m_distance(std::move(distance)),
7275     m_condition(std::move(condition))
7276 {
7277     m_root_candidate_invariant =
7278         m_distance->RootCandidateInvariant() &&
7279         m_condition->RootCandidateInvariant();
7280     m_target_invariant =
7281         m_distance->TargetInvariant() &&
7282         m_condition->TargetInvariant();
7283     m_source_invariant =
7284         m_distance->SourceInvariant() &&
7285         m_condition->SourceInvariant();
7286 }
7287 
operator ==(const Condition & rhs) const7288 bool WithinDistance::operator==(const Condition& rhs) const {
7289     if (this == &rhs)
7290         return true;
7291     if (typeid(*this) != typeid(rhs))
7292         return false;
7293 
7294     const WithinDistance& rhs_ = static_cast<const WithinDistance&>(rhs);
7295 
7296     CHECK_COND_VREF_MEMBER(m_distance)
7297     CHECK_COND_VREF_MEMBER(m_condition)
7298 
7299     return true;
7300 }
7301 
7302 namespace {
7303     struct WithinDistanceSimpleMatch {
WithinDistanceSimpleMatchCondition::__anone9a56faf6611::WithinDistanceSimpleMatch7304         WithinDistanceSimpleMatch(const ObjectSet& from_objects, double distance) :
7305             m_from_objects(from_objects),
7306             m_distance2(distance*distance)
7307         {}
7308 
operator ()Condition::__anone9a56faf6611::WithinDistanceSimpleMatch7309         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
7310             if (!candidate)
7311                 return false;
7312 
7313             // is candidate object close enough to any of the passed-in objects?
7314             for (auto& obj : m_from_objects) {
7315                 double delta_x = candidate->X() - obj->X();
7316                 double delta_y = candidate->Y() - obj->Y();
7317                 if (delta_x*delta_x + delta_y*delta_y <= m_distance2)
7318                     return true;
7319             }
7320 
7321             return false;
7322         }
7323 
7324         const ObjectSet& m_from_objects;
7325         double m_distance2;
7326     };
7327 }
7328 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const7329 void WithinDistance::Eval(const ScriptingContext& parent_context,
7330                           ObjectSet& matches, ObjectSet& non_matches,
7331                           SearchDomain search_domain/* = NON_MATCHES*/) const
7332 {
7333     bool simple_eval_safe = m_distance->LocalCandidateInvariant() &&
7334                    	        (parent_context.condition_root_candidate || RootCandidateInvariant());
7335     if (simple_eval_safe) {
7336         // evaluate contained objects and distance once and check for all candidates
7337         TraceLogger(conditions) << "WithinDistance::Eval simple case";
7338 
7339         // get subcondition matches
7340         ObjectSet subcondition_matches;
7341         m_condition->Eval(parent_context, subcondition_matches);
7342         double distance = m_distance->Eval(parent_context);
7343 
7344         // need to check locations (with respect to subcondition matches) of candidates separately
7345         EvalImpl(matches, non_matches, search_domain, WithinDistanceSimpleMatch(subcondition_matches, distance));
7346     } else {
7347         // re-evaluate contained objects for each candidate object
7348         TraceLogger(conditions) << "WithinDistance::Eval full case";
7349         Condition::Eval(parent_context, matches, non_matches, search_domain);
7350     }
7351 }
7352 
Description(bool negated) const7353 std::string WithinDistance::Description(bool negated/* = false*/) const {
7354     std::string value_str = m_distance->ConstantExpr() ?
7355                                 std::to_string(m_distance->Eval()) :
7356                                 m_distance->Description();
7357     return str(FlexibleFormat((!negated)
7358         ? UserString("DESC_WITHIN_DISTANCE")
7359         : UserString("DESC_WITHIN_DISTANCE_NOT"))
7360                % value_str
7361                % m_condition->Description());
7362 }
7363 
Dump(unsigned short ntabs) const7364 std::string WithinDistance::Dump(unsigned short ntabs) const {
7365     std::string retval = DumpIndent(ntabs) + "WithinDistance distance = " + m_distance->Dump(ntabs) + " condition =\n";
7366     retval += m_condition->Dump(ntabs+1);
7367     return retval;
7368 }
7369 
Match(const ScriptingContext & local_context) const7370 bool WithinDistance::Match(const ScriptingContext& local_context) const {
7371     auto candidate = local_context.condition_local_candidate;
7372     if (!candidate) {
7373         ErrorLogger() << "WithinDistance::Match passed no candidate object";
7374         return false;
7375     }
7376 
7377     // get subcondition matches
7378     ObjectSet subcondition_matches;
7379     m_condition->Eval(local_context, subcondition_matches);
7380     if (subcondition_matches.empty())
7381         return false;
7382 
7383     return WithinDistanceSimpleMatch(subcondition_matches, m_distance->Eval(local_context))(candidate);
7384 }
7385 
SetTopLevelContent(const std::string & content_name)7386 void WithinDistance::SetTopLevelContent(const std::string& content_name) {
7387     if (m_distance)
7388         m_distance->SetTopLevelContent(content_name);
7389     if (m_condition)
7390         m_condition->SetTopLevelContent(content_name);
7391 }
7392 
GetCheckSum() const7393 unsigned int WithinDistance::GetCheckSum() const {
7394     unsigned int retval{0};
7395 
7396     CheckSums::CheckSumCombine(retval, "Condition::WithinDistance");
7397     CheckSums::CheckSumCombine(retval, m_distance);
7398     CheckSums::CheckSumCombine(retval, m_condition);
7399 
7400     TraceLogger() << "GetCheckSum(WithinDistance): retval: " << retval;
7401     return retval;
7402 }
7403 
7404 ///////////////////////////////////////////////////////////
7405 // WithinStarlaneJumps                                   //
7406 ///////////////////////////////////////////////////////////
WithinStarlaneJumps(std::unique_ptr<ValueRef::ValueRef<int>> && jumps,std::unique_ptr<Condition> && condition)7407 WithinStarlaneJumps::WithinStarlaneJumps(std::unique_ptr<ValueRef::ValueRef<int>>&& jumps,
7408                                          std::unique_ptr<Condition>&& condition) :
7409     Condition(),
7410     m_jumps(std::move(jumps)),
7411     m_condition(std::move(condition))
7412 {
7413     m_root_candidate_invariant = m_jumps->RootCandidateInvariant() && m_condition->RootCandidateInvariant();
7414     m_target_invariant = m_jumps->TargetInvariant() && m_condition->TargetInvariant();
7415     m_source_invariant = m_jumps->SourceInvariant() && m_condition->SourceInvariant();
7416 }
7417 
operator ==(const Condition & rhs) const7418 bool WithinStarlaneJumps::operator==(const Condition& rhs) const {
7419     if (this == &rhs)
7420         return true;
7421     if (typeid(*this) != typeid(rhs))
7422         return false;
7423 
7424     const WithinStarlaneJumps& rhs_ = static_cast<const WithinStarlaneJumps&>(rhs);
7425 
7426     CHECK_COND_VREF_MEMBER(m_jumps)
7427     CHECK_COND_VREF_MEMBER(m_condition)
7428 
7429     return true;
7430 }
7431 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const7432 void WithinStarlaneJumps::Eval(const ScriptingContext& parent_context,
7433                                ObjectSet& matches, ObjectSet& non_matches,
7434                                SearchDomain search_domain/* = NON_MATCHES*/) const
7435 {
7436     bool simple_eval_safe = m_jumps->LocalCandidateInvariant() &&
7437                             (parent_context.condition_root_candidate || RootCandidateInvariant());
7438     if (simple_eval_safe) {
7439         // evaluate contained objects and jumps limit once and check for all candidates
7440 
7441         // get subcondition matches
7442         ObjectSet subcondition_matches;
7443         m_condition->Eval(parent_context, subcondition_matches);
7444         int jump_limit = m_jumps->Eval(parent_context);
7445         ObjectSet &from_set(search_domain == MATCHES ? matches : non_matches);
7446 
7447         std::tie(matches, non_matches) = GetPathfinder()->WithinJumpsOfOthers(jump_limit, from_set, subcondition_matches);
7448 
7449     } else {
7450         // re-evaluate contained objects for each candidate object
7451         Condition::Eval(parent_context, matches, non_matches, search_domain);
7452     }
7453 }
7454 
Description(bool negated) const7455 std::string WithinStarlaneJumps::Description(bool negated/* = false*/) const {
7456     std::string value_str = m_jumps->ConstantExpr() ? std::to_string(m_jumps->Eval()) : m_jumps->Description();
7457     return str(FlexibleFormat((!negated)
7458         ? UserString("DESC_WITHIN_STARLANE_JUMPS")
7459         : UserString("DESC_WITHIN_STARLANE_JUMPS_NOT"))
7460                % value_str
7461                % m_condition->Description());
7462 }
7463 
Dump(unsigned short ntabs) const7464 std::string WithinStarlaneJumps::Dump(unsigned short ntabs) const {
7465     std::string retval = DumpIndent(ntabs) + "WithinStarlaneJumps jumps = " + m_jumps->Dump(ntabs) + " condition =\n";
7466     retval += m_condition->Dump(ntabs+1);
7467     return retval;
7468 }
7469 
Match(const ScriptingContext & local_context) const7470 bool WithinStarlaneJumps::Match(const ScriptingContext& local_context) const {
7471     auto candidate = local_context.condition_local_candidate;
7472     if (!candidate) {
7473         ErrorLogger() << "WithinStarlaneJumps::Match passed no candidate object";
7474         return false;
7475     }
7476 
7477     // get subcondition matches
7478     ObjectSet subcondition_matches;
7479     m_condition->Eval(local_context, subcondition_matches);
7480     if (subcondition_matches.empty())
7481         return false;
7482 
7483     int jump_limit = m_jumps->Eval(local_context);
7484     if (jump_limit < 0)
7485         return false;
7486 
7487     ObjectSet candidate_set{candidate};
7488 
7489     // candidate objects within jumps of subcondition_matches objects
7490     ObjectSet near_objs;
7491 
7492     std::tie(near_objs, std::ignore) =
7493         GetPathfinder()->WithinJumpsOfOthers(jump_limit, candidate_set, subcondition_matches);
7494     return !near_objs.empty();
7495 }
7496 
SetTopLevelContent(const std::string & content_name)7497 void WithinStarlaneJumps::SetTopLevelContent(const std::string& content_name) {
7498     if (m_jumps)
7499         m_jumps->SetTopLevelContent(content_name);
7500     if (m_condition)
7501         m_condition->SetTopLevelContent(content_name);
7502 }
7503 
GetCheckSum() const7504 unsigned int WithinStarlaneJumps::GetCheckSum() const {
7505     unsigned int retval{0};
7506 
7507     CheckSums::CheckSumCombine(retval, "Condition::WithinStarlaneJumps");
7508     CheckSums::CheckSumCombine(retval, m_jumps);
7509     CheckSums::CheckSumCombine(retval, m_condition);
7510 
7511     TraceLogger() << "GetCheckSum(WithinStarlaneJumps): retval: " << retval;
7512     return retval;
7513 }
7514 
7515 ///////////////////////////////////////////////////////////
7516 // CanAddStarlaneConnection                              //
7517 ///////////////////////////////////////////////////////////
CanAddStarlaneConnection(std::unique_ptr<Condition> && condition)7518 CanAddStarlaneConnection::CanAddStarlaneConnection(std::unique_ptr<Condition>&& condition) :
7519     Condition(),
7520     m_condition(std::move(condition))
7521 {
7522     m_root_candidate_invariant = m_condition->RootCandidateInvariant();
7523     m_target_invariant = m_condition->TargetInvariant();
7524     m_source_invariant = m_condition->SourceInvariant();
7525 }
7526 
operator ==(const Condition & rhs) const7527 bool CanAddStarlaneConnection::operator==(const Condition& rhs) const {
7528     if (this == &rhs)
7529         return true;
7530     if (typeid(*this) != typeid(rhs))
7531         return false;
7532 
7533     const CanAddStarlaneConnection& rhs_ = static_cast<const CanAddStarlaneConnection&>(rhs);
7534 
7535     CHECK_COND_VREF_MEMBER(m_condition)
7536 
7537     return true;
7538 }
7539 
7540 namespace {
7541     // check if two destination systems, connected to the same origin system
7542     // would have starlanes too close angularly to eachother
LanesAngularlyTooClose(std::shared_ptr<const UniverseObject> sys1,std::shared_ptr<const UniverseObject> lane1_sys2,std::shared_ptr<const UniverseObject> lane2_sys2)7543     bool LanesAngularlyTooClose(std::shared_ptr<const UniverseObject> sys1,
7544                                 std::shared_ptr<const UniverseObject> lane1_sys2,
7545                                 std::shared_ptr<const UniverseObject> lane2_sys2)
7546     {
7547         if (!sys1 || !lane1_sys2 || !lane2_sys2)
7548             return true;
7549         if (sys1 == lane1_sys2 || sys1 == lane2_sys2 || lane1_sys2 == lane2_sys2)
7550             return true;
7551 
7552         float dx1 = lane1_sys2->X() - sys1->X();
7553         float dy1 = lane1_sys2->Y() - sys1->Y();
7554         float mag = std::sqrt(dx1*dx1 + dy1*dy1);
7555         if (mag == 0.0f)
7556             return true;
7557         dx1 /= mag;
7558         dy1 /= mag;
7559 
7560         float dx2 = lane2_sys2->X() - sys1->X();
7561         float dy2 = lane2_sys2->Y() - sys1->Y();
7562         mag = std::sqrt(dx2*dx2 + dy2*dy2);
7563         if (mag == 0.0f)
7564             return true;
7565         dx2 /= mag;
7566         dy2 /= mag;
7567 
7568 
7569         const float MAX_LANE_DOT_PRODUCT = 0.87f;   // magic limit adjusted to allow no more than 12 starlanes from a system
7570                                                     // arccos(0.87) = 0.515594 rad = 29.5 degrees
7571 
7572         float dp = (dx1 * dx2) + (dy1 * dy2);
7573         //TraceLogger() << "systems: " << sys1->UniverseObject::Name() << "  " << lane1_sys2->UniverseObject::Name() << "  " << lane2_sys2->UniverseObject::Name() << "  dp: " << dp << std::endl;
7574 
7575         return dp >= MAX_LANE_DOT_PRODUCT;   // if dot product too high after normalizing vectors, angles are adequately separated
7576     }
7577 
7578     // check the distance between a system and a (possibly nonexistant)
7579     // starlane between two other systems. distance here is how far the third
7580     // system is from the line passing through the lane endpoint systems, as
7581     // long as the third system is closer to either end point than the endpoints
7582     // are to eachother. if the third system is further than the endpoints, than
7583     // the distance to the line is not considered and the lane is considered
7584     // acceptable
ObjectTooCloseToLane(std::shared_ptr<const UniverseObject> lane_end_sys1,std::shared_ptr<const UniverseObject> lane_end_sys2,std::shared_ptr<const UniverseObject> obj)7585     bool ObjectTooCloseToLane(std::shared_ptr<const UniverseObject> lane_end_sys1,
7586                               std::shared_ptr<const UniverseObject> lane_end_sys2,
7587                               std::shared_ptr<const UniverseObject> obj)
7588     {
7589         if (!lane_end_sys1 || !lane_end_sys2 || !obj)
7590             return true;
7591         if (lane_end_sys1 == lane_end_sys2 || obj == lane_end_sys1 || obj == lane_end_sys2)
7592             return true;
7593 
7594         // check distances (squared) between object and lane-end systems
7595         float v_12_x = lane_end_sys2->X() - lane_end_sys1->X();
7596         float v_12_y = lane_end_sys2->Y() - lane_end_sys1->Y();
7597         float v_o1_x = lane_end_sys1->X() - obj->X();
7598         float v_o1_y = lane_end_sys1->Y() - obj->Y();
7599         float v_o2_x = lane_end_sys2->X() - obj->X();
7600         float v_o2_y = lane_end_sys2->Y() - obj->Y();
7601 
7602         float dist2_12 = v_12_x*v_12_x + v_12_y*v_12_y;
7603         float dist2_o1 = v_o1_x*v_o1_x + v_o1_y*v_o1_y;
7604         float dist2_o2 = v_o2_x*v_o2_x + v_o2_y*v_o2_y;
7605 
7606         // object to zero-length lanes
7607         if (dist2_12 == 0.0f || dist2_o1 == 0.0f || dist2_o2 == 0.0f)
7608             return true;
7609 
7610         // if object is further from either of the lane end systems than they
7611         // are from eachother, it is fine, regardless of the right-angle
7612         // distance to the line between the systems
7613         if (dist2_12 < dist2_o1 || dist2_12 < dist2_o2)
7614             return false;
7615 
7616 
7617         // check right-angle distance between obj and lane
7618 
7619         // normalize vector components of lane vector
7620         float mag_12 = std::sqrt(dist2_12);
7621         if (mag_12 == 0.0f)
7622             return true;
7623         v_12_x /= mag_12;
7624         v_12_y /= mag_12;
7625 
7626         // distance to point from line from vector projection / dot products
7627         //.......O
7628         //      /|
7629         //     / |
7630         //    /  |d
7631         //   /   |
7632         //  /a___|___
7633         // 1         2
7634         // (1O).(12) = |1O| |12| cos(a)
7635         // d = |1O| cos(a) = (1O).(12) / |12|
7636         // d = -(O1).(12 / |12|)
7637 
7638         const float MIN_PERP_DIST = 20; // magic limit, in units of universe units (uu)
7639 
7640         float perp_dist = std::abs(v_o1_x*v_12_x + v_o1_y*v_12_y);
7641 
7642         return perp_dist < MIN_PERP_DIST;
7643     }
7644 
CrossProduct(float dx1,float dy1,float dx2,float dy2)7645     inline float CrossProduct(float dx1, float dy1, float dx2, float dy2)
7646     { return dx1*dy2 - dy1*dx2; }
7647 
LanesCross(std::shared_ptr<const System> lane1_end_sys1,std::shared_ptr<const System> lane1_end_sys2,std::shared_ptr<const System> lane2_end_sys1,std::shared_ptr<const System> lane2_end_sys2)7648     bool LanesCross(std::shared_ptr<const System> lane1_end_sys1,
7649                     std::shared_ptr<const System> lane1_end_sys2,
7650                     std::shared_ptr<const System> lane2_end_sys1,
7651                     std::shared_ptr<const System> lane2_end_sys2)
7652     {
7653         // are all endpoints valid systems?
7654         if (!lane1_end_sys1 || !lane1_end_sys2 || !lane2_end_sys1 || !lane2_end_sys2)
7655             return false;
7656 
7657         // is either lane degenerate (same start and endpoints)
7658         if (lane1_end_sys1 == lane1_end_sys2 || lane2_end_sys1 == lane2_end_sys2)
7659             return false;
7660 
7661         // do the two lanes share endpoints?
7662         bool share_endpoint_1 = lane1_end_sys1 == lane2_end_sys1 || lane1_end_sys1 == lane2_end_sys2;
7663         bool share_endpoint_2 = lane1_end_sys2 == lane2_end_sys1 || lane1_end_sys2 == lane2_end_sys2;
7664         if (share_endpoint_1 && share_endpoint_2)
7665             return true;    // two copies of the same lane?
7666         if (share_endpoint_1 || share_endpoint_2)
7667             return false;   // one common endpoing, but not both common, so can't cross in middle
7668 
7669         // calculate vector components for lanes
7670         // lane 1
7671         float v_11_12_x = lane1_end_sys2->X() - lane1_end_sys1->X();
7672         float v_11_12_y = lane1_end_sys2->Y() - lane1_end_sys1->Y();
7673         // lane 2
7674         float v_21_22_x = lane2_end_sys2->X() - lane2_end_sys1->X();
7675         float v_21_22_y = lane2_end_sys2->Y() - lane2_end_sys1->Y();
7676 
7677         // calculate vector components from lane 1 system 1 to lane 2 endpoints
7678         // lane 1 endpoint 1 to lane 2 endpoint 1
7679         float v_11_21_x = lane2_end_sys1->X() - lane1_end_sys1->X();
7680         float v_11_21_y = lane2_end_sys1->Y() - lane1_end_sys1->Y();
7681         // lane 1 endpoint 1 to lane 2 endpoint 2
7682         float v_11_22_x = lane2_end_sys2->X() - lane1_end_sys1->X();
7683         float v_11_22_y = lane2_end_sys2->Y() - lane1_end_sys1->Y();
7684 
7685         // find cross products of vectors to check on which sides of lane 1 the
7686         // endpoints of lane 2 are located...
7687         float cp_1_21 = CrossProduct(v_11_12_x, v_11_12_y, v_11_21_x, v_11_21_y);
7688         float cp_1_22 = CrossProduct(v_11_12_x, v_11_12_y, v_11_22_x, v_11_22_y);
7689         if (cp_1_21*cp_1_22 >= 0) // product of same sign numbers is positive, of different sign numbers is negative
7690             return false;   // if same sign, points are on same side of line, so can't cross it
7691 
7692         // calculate vector components from lane 2 system 1 to lane 1 endpoints
7693         // lane 2 endpoint 1 to lane 1 endpoint 1
7694         float v_21_11_x = -v_11_21_x;
7695         float v_21_11_y = -v_11_21_y;
7696         // lane 2 endpoint 1 to lane 1 endpoint 2
7697         float v_21_12_x = lane1_end_sys2->X() - lane2_end_sys1->X();
7698         float v_21_12_y = lane1_end_sys2->Y() - lane2_end_sys1->Y();
7699 
7700         // find cross products of vectors to check on which sides of lane 2 the
7701         // endpoints of lane 1 are located...
7702         float cp_2_11 = CrossProduct(v_21_22_x, v_21_22_y, v_21_11_x, v_21_11_y);
7703         float cp_2_12 = CrossProduct(v_21_22_x, v_21_22_y, v_21_12_x, v_21_12_y);
7704         if (cp_2_11*cp_2_12 >= 0)
7705             return false;
7706 
7707         // endpoints of both lines are on opposite sides of the other line, so
7708         // the lines must cross
7709 
7710         return true;
7711     }
7712 
LaneCrossesExistingLane(std::shared_ptr<const System> lane_end_sys1,std::shared_ptr<const System> lane_end_sys2,const ObjectMap & objects)7713     bool LaneCrossesExistingLane(std::shared_ptr<const System> lane_end_sys1,
7714                                  std::shared_ptr<const System> lane_end_sys2,
7715                                  const ObjectMap& objects)
7716     {
7717         if (!lane_end_sys1 || !lane_end_sys2 || lane_end_sys1 == lane_end_sys2)
7718             return true;
7719 
7720         // loop over all existing lanes in all systems, checking if a lane
7721         // beween the specified systems would cross any of the existing lanes
7722         for (auto& system : objects.all<System>()) {
7723             if (system == lane_end_sys1 || system == lane_end_sys2)
7724                 continue;
7725 
7726             const auto& sys_existing_lanes = system->StarlanesWormholes();
7727 
7728             // check all existing lanes of currently-being-checked system
7729             for (const auto& lane : sys_existing_lanes) {
7730                 auto lane_end_sys3 = objects.get<System>(lane.first);
7731                 if (!lane_end_sys3)
7732                     continue;
7733                 // don't need to check against existing lanes that include one
7734                 // of the endpoints of the lane is one of the specified systems
7735                 if (lane_end_sys3 == lane_end_sys1 || lane_end_sys3 == lane_end_sys2)
7736                     continue;
7737 
7738                 if (LanesCross(lane_end_sys1, lane_end_sys2, system, lane_end_sys3)) {
7739                     //TraceLogger() << "... ... ... lane from: " << lane_end_sys1->UniverseObject::Name() << " to: " << lane_end_sys2->UniverseObject::Name()
7740                     //          << " crosses lane from: " << system->UniverseObject::Name() << " to: " << lane_end_sys3->UniverseObject::Name() << std::endl;
7741                     return true;
7742                 }
7743             }
7744         }
7745 
7746         return false;
7747     }
7748 
LaneTooCloseToOtherSystem(std::shared_ptr<const System> lane_end_sys1,std::shared_ptr<const System> lane_end_sys2,const ObjectMap & objects)7749     bool LaneTooCloseToOtherSystem(std::shared_ptr<const System> lane_end_sys1,
7750                                    std::shared_ptr<const System> lane_end_sys2,
7751                                    const ObjectMap& objects)
7752     {
7753         if (!lane_end_sys1 || !lane_end_sys2 || lane_end_sys1 == lane_end_sys2)
7754             return true;
7755 
7756         // loop over all existing systems, checking if each is too close to a
7757         // lane between the specified lane endpoints
7758         for (auto& system : objects.all<System>()) {
7759             if (system == lane_end_sys1 || system == lane_end_sys2)
7760                 continue;
7761 
7762             if (ObjectTooCloseToLane(lane_end_sys1, lane_end_sys2, system))
7763                 return true;
7764         }
7765 
7766         return false;
7767     }
7768 
7769     struct CanAddStarlaneConnectionSimpleMatch {
CanAddStarlaneConnectionSimpleMatchCondition::__anone9a56faf6711::CanAddStarlaneConnectionSimpleMatch7770         CanAddStarlaneConnectionSimpleMatch(const ObjectSet& destination_objects, const ObjectMap& objects) :
7771             m_destination_systems(),
7772             m_objects(objects)
7773         {
7774             // get (one of each of) set of systems that are or that contain any
7775             // destination objects
7776             std::set<std::shared_ptr<const System>> dest_systems;
7777             for (auto& obj : destination_objects) {
7778                 if (auto sys = m_objects.get<System>(obj->SystemID()))
7779                     dest_systems.insert(sys);
7780             }
7781             std::copy(dest_systems.begin(), dest_systems.end(), std::inserter(m_destination_systems, m_destination_systems.end()));
7782         }
7783 
operator ()Condition::__anone9a56faf6711::CanAddStarlaneConnectionSimpleMatch7784         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
7785             if (!candidate)
7786                 return false;
7787 
7788             // get system from candidate
7789             auto candidate_sys = std::dynamic_pointer_cast<const System>(candidate);
7790             if (!candidate_sys)
7791                 candidate_sys = m_objects.get<System>(candidate->SystemID());
7792             if (!candidate_sys)
7793                 return false;
7794 
7795 
7796             // check if candidate is one of the destination systems
7797             for (auto& destination : m_destination_systems) {
7798                 if (candidate_sys->ID() == destination->ID())
7799                     return false;
7800             }
7801 
7802 
7803             // check if candidate already has a lane to any of the destination systems
7804             for (auto& destination : m_destination_systems) {
7805                 if (candidate_sys->HasStarlaneTo(destination->ID()))
7806                     return false;
7807             }
7808 
7809             // check if any of the proposed lanes are too close to any already-
7810             // present lanes of the candidate system
7811             //TraceLogger() << "... Checking lanes of candidate system: " << candidate->UniverseObject::Name() << std::endl;
7812             for (const auto& lane : candidate_sys->StarlanesWormholes()) {
7813                 auto candidate_existing_lane_end_sys = m_objects.get<System>(lane.first);
7814                 if (!candidate_existing_lane_end_sys)
7815                     continue;
7816 
7817                 // check this existing lane against potential lanes to all destination systems
7818                 for (auto& dest_sys : m_destination_systems) {
7819                     if (LanesAngularlyTooClose(candidate_sys, candidate_existing_lane_end_sys, dest_sys)) {
7820                         //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->UniverseObject::Name() << " to " << dest_sys->UniverseObject::Name() << " due to existing lane to " << candidate_existing_lane_end_sys->UniverseObject::Name() << std::endl;
7821                         return false;
7822                     }
7823                 }
7824             }
7825 
7826 
7827             // check if any of the proposed lanes are too close to any already-
7828             // present lanes of any of the destination systems
7829             //TraceLogger() << "... Checking lanes of destination systems:" << std::endl;
7830             for (auto& dest_sys : m_destination_systems) {
7831                 // check this destination system's existing lanes against a lane
7832                 // to the candidate system
7833                 for (const auto& dest_lane : dest_sys->StarlanesWormholes()) {
7834                     auto dest_lane_end_sys = m_objects.get<System>(dest_lane.first);
7835                     if (!dest_lane_end_sys)
7836                         continue;
7837 
7838                     if (LanesAngularlyTooClose(dest_sys, candidate_sys, dest_lane_end_sys)) {
7839                         //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->UniverseObject::Name() << " to " << dest_sys->UniverseObject::Name() << " due to existing lane from dest to " << dest_lane_end_sys->UniverseObject::Name() << std::endl;
7840                         return false;
7841                     }
7842                 }
7843             }
7844 
7845 
7846             // check if any of the proposed lanes are too close to eachother
7847             //TraceLogger() << "... Checking proposed lanes against eachother" << std::endl;
7848             for (auto it1 = m_destination_systems.begin();
7849                  it1 != m_destination_systems.end(); ++it1)
7850             {
7851                 auto dest_sys1 = *it1;
7852 
7853                 // don't need to check a lane in both directions, so start at one past it1
7854                 auto it2 = it1;
7855                 ++it2;
7856                 for (; it2 != m_destination_systems.end(); ++it2) {
7857                     auto dest_sys2 = *it2;
7858                     if (LanesAngularlyTooClose(candidate_sys, dest_sys1, dest_sys2)) {
7859                         //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->UniverseObject::Name() << " to " << dest_sys1->UniverseObject::Name() << " and also to " << dest_sys2->UniverseObject::Name() << std::endl;
7860                         return false;
7861                     }
7862                 }
7863             }
7864 
7865 
7866             // check that the proposed lanes are not too close to any existing
7867             // system they are not connected to
7868             //TraceLogger() << "... Checking proposed lanes for proximity to other systems" <<std::endl;
7869             for (auto& dest_sys : m_destination_systems) {
7870                 if (LaneTooCloseToOtherSystem(candidate_sys, dest_sys, m_objects)) {
7871                     //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->Name() << " to " << dest_sys->Name() << " due to proximity to another system." << std::endl;
7872                     return false;
7873                 }
7874             }
7875 
7876 
7877             // check that there are no lanes already existing that cross the proposed lanes
7878             //TraceLogger() << "... Checking for potential lanes crossing existing lanes" << std::endl;
7879             for (auto& dest_sys : m_destination_systems) {
7880                 if (LaneCrossesExistingLane(candidate_sys, dest_sys, m_objects)) {
7881                     //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->Name() << " to " << dest_sys->Name() << " due to crossing an existing lane." << std::endl;
7882                     return false;
7883                 }
7884             }
7885 
7886             return true;
7887         }
7888 
7889         std::vector<std::shared_ptr<const System>> m_destination_systems;
7890         const ObjectMap& m_objects;
7891     };
7892 }
7893 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const7894 void CanAddStarlaneConnection::Eval(const ScriptingContext& parent_context,
7895                                     ObjectSet& matches, ObjectSet& non_matches,
7896                                     SearchDomain search_domain/* = NON_MATCHES*/) const
7897 {
7898     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
7899     if (simple_eval_safe) {
7900         // evaluate contained objects once and check for all candidates
7901 
7902         // get subcondition matches
7903         ObjectSet subcondition_matches;
7904         m_condition->Eval(parent_context, subcondition_matches);
7905 
7906         EvalImpl(matches, non_matches, search_domain, CanAddStarlaneConnectionSimpleMatch(subcondition_matches, parent_context.ContextObjects()));
7907     } else {
7908         // re-evaluate contained objects for each candidate object
7909         Condition::Eval(parent_context, matches, non_matches, search_domain);
7910     }
7911 }
7912 
Description(bool negated) const7913 std::string CanAddStarlaneConnection::Description(bool negated/* = false*/) const {
7914     return str(FlexibleFormat((!negated)
7915         ? UserString("DESC_CAN_ADD_STARLANE_CONNECTION") : UserString("DESC_CAN_ADD_STARLANE_CONNECTION_NOT"))
7916         % m_condition->Description());
7917 }
7918 
Dump(unsigned short ntabs) const7919 std::string CanAddStarlaneConnection::Dump(unsigned short ntabs) const {
7920     std::string retval = DumpIndent(ntabs) + "CanAddStarlanesTo condition =\n";
7921     retval += m_condition->Dump(ntabs+1);
7922     return retval;
7923 }
7924 
Match(const ScriptingContext & local_context) const7925 bool CanAddStarlaneConnection::Match(const ScriptingContext& local_context) const {
7926     auto candidate = local_context.condition_local_candidate;
7927     if (!candidate) {
7928         ErrorLogger() << "CanAddStarlaneConnection::Match passed no candidate object";
7929         return false;
7930     }
7931 
7932     // get subcondition matches
7933     ObjectSet subcondition_matches;
7934     m_condition->Eval(local_context, subcondition_matches);
7935 
7936     return CanAddStarlaneConnectionSimpleMatch(subcondition_matches, local_context.ContextObjects())(candidate);
7937 }
7938 
SetTopLevelContent(const std::string & content_name)7939 void CanAddStarlaneConnection::SetTopLevelContent(const std::string& content_name) {
7940     if (m_condition)
7941         m_condition->SetTopLevelContent(content_name);
7942 }
7943 
GetCheckSum() const7944 unsigned int CanAddStarlaneConnection::GetCheckSum() const {
7945     unsigned int retval{0};
7946 
7947     CheckSums::CheckSumCombine(retval, "Condition::CanAddStarlaneConnection");
7948     CheckSums::CheckSumCombine(retval, m_condition);
7949 
7950     TraceLogger() << "GetCheckSum(CanAddStarlaneConnection): retval: " << retval;
7951     return retval;
7952 }
7953 
7954 ///////////////////////////////////////////////////////////
7955 // ExploredByEmpire                                      //
7956 ///////////////////////////////////////////////////////////
ExploredByEmpire(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id)7957 ExploredByEmpire::ExploredByEmpire(std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id) :
7958     Condition(),
7959     m_empire_id(std::move(empire_id))
7960 {
7961     m_root_candidate_invariant = m_empire_id->RootCandidateInvariant();
7962     m_target_invariant = m_empire_id->TargetInvariant();
7963     m_source_invariant = m_empire_id->SourceInvariant();
7964 }
7965 
operator ==(const Condition & rhs) const7966 bool ExploredByEmpire::operator==(const Condition& rhs) const {
7967     if (this == &rhs)
7968         return true;
7969     if (typeid(*this) != typeid(rhs))
7970         return false;
7971 
7972     const ExploredByEmpire& rhs_ = static_cast<const ExploredByEmpire&>(rhs);
7973 
7974     CHECK_COND_VREF_MEMBER(m_empire_id)
7975 
7976     return true;
7977 }
7978 
7979 namespace {
7980     struct ExploredByEmpireSimpleMatch {
ExploredByEmpireSimpleMatchCondition::__anone9a56faf6811::ExploredByEmpireSimpleMatch7981         ExploredByEmpireSimpleMatch(int empire_id) :
7982             m_empire_id(empire_id)
7983         {}
7984 
operator ()Condition::__anone9a56faf6811::ExploredByEmpireSimpleMatch7985         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
7986             if (!candidate)
7987                 return false;
7988 
7989             const Empire* empire = GetEmpire(m_empire_id);
7990             if (!empire)
7991                 return false;
7992             return empire->HasExploredSystem(candidate->ID());
7993         }
7994 
7995         int m_empire_id;
7996     };
7997 }
7998 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const7999 void ExploredByEmpire::Eval(const ScriptingContext& parent_context,
8000                             ObjectSet& matches, ObjectSet& non_matches,
8001                             SearchDomain search_domain/* = NON_MATCHES*/) const
8002 {
8003     bool simple_eval_safe = m_empire_id->ConstantExpr() ||
8004                             (m_empire_id->LocalCandidateInvariant() &&
8005                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
8006     if (simple_eval_safe) {
8007         // evaluate empire id once, and use to check all candidate objects
8008         int empire_id = m_empire_id->Eval(parent_context);
8009 
8010         // need to check each candidate separately to test if it has been explored
8011         EvalImpl(matches, non_matches, search_domain, ExploredByEmpireSimpleMatch(empire_id));
8012     } else {
8013         // re-evaluate empire id for each candidate object
8014         Condition::Eval(parent_context, matches, non_matches, search_domain);
8015     }
8016 }
8017 
Description(bool negated) const8018 std::string ExploredByEmpire::Description(bool negated/* = false*/) const {
8019     std::string empire_str;
8020     if (m_empire_id) {
8021         int empire_id = ALL_EMPIRES;
8022         if (m_empire_id->ConstantExpr())
8023             empire_id = m_empire_id->Eval();
8024         if (const Empire* empire = GetEmpire(empire_id))
8025             empire_str = empire->Name();
8026         else
8027             empire_str = m_empire_id->Description();
8028     }
8029 
8030     return str(FlexibleFormat((!negated)
8031                ? UserString("DESC_EXPLORED_BY_EMPIRE")
8032                : UserString("DESC_EXPLORED_BY_EMPIRE_NOT"))
8033                % empire_str);
8034 }
8035 
Dump(unsigned short ntabs) const8036 std::string ExploredByEmpire::Dump(unsigned short ntabs) const
8037 { return DumpIndent(ntabs) + "ExploredByEmpire empire_id = " + m_empire_id->Dump(ntabs); }
8038 
Match(const ScriptingContext & local_context) const8039 bool ExploredByEmpire::Match(const ScriptingContext& local_context) const {
8040     auto candidate = local_context.condition_local_candidate;
8041     if (!candidate) {
8042         ErrorLogger() << "ExploredByEmpire::Match passed no candidate object";
8043         return false;
8044     }
8045 
8046     return ExploredByEmpireSimpleMatch(m_empire_id->Eval(local_context))(candidate);
8047 }
8048 
SetTopLevelContent(const std::string & content_name)8049 void ExploredByEmpire::SetTopLevelContent(const std::string& content_name) {
8050     if (m_empire_id)
8051         m_empire_id->SetTopLevelContent(content_name);
8052 }
8053 
GetCheckSum() const8054 unsigned int ExploredByEmpire::GetCheckSum() const {
8055     unsigned int retval{0};
8056 
8057     CheckSums::CheckSumCombine(retval, "Condition::ExploredByEmpire");
8058     CheckSums::CheckSumCombine(retval, m_empire_id);
8059 
8060     TraceLogger() << "GetCheckSum(ExploredByEmpire): retval: " << retval;
8061     return retval;
8062 }
8063 
8064 ///////////////////////////////////////////////////////////
8065 // Stationary                                            //
8066 ///////////////////////////////////////////////////////////
Stationary()8067 Stationary::Stationary() :
8068     Condition()
8069 {
8070     m_root_candidate_invariant = true;
8071     m_target_invariant = true;
8072     m_source_invariant = true;
8073 }
8074 
operator ==(const Condition & rhs) const8075 bool Stationary::operator==(const Condition& rhs) const
8076 { return Condition::operator==(rhs); }
8077 
Description(bool negated) const8078 std::string Stationary::Description(bool negated/* = false*/) const {
8079     return (!negated)
8080         ? UserString("DESC_STATIONARY")
8081         : UserString("DESC_STATIONARY_NOT");
8082 }
8083 
Dump(unsigned short ntabs) const8084 std::string Stationary::Dump(unsigned short ntabs) const
8085 { return DumpIndent(ntabs) + "Stationary\n"; }
8086 
Match(const ScriptingContext & local_context) const8087 bool Stationary::Match(const ScriptingContext& local_context) const {
8088     auto candidate = local_context.condition_local_candidate;
8089     if (!candidate) {
8090         ErrorLogger() << "Stationary::Match passed no candidate object";
8091         return false;
8092     }
8093 
8094     // the only objects that can move are fleets and the ships in them.  so,
8095     // attempt to cast the candidate object to a fleet or ship, and if it's a ship
8096     // get the fleet of that ship
8097     auto fleet = std::dynamic_pointer_cast<const Fleet>(candidate);
8098     if (!fleet)
8099         if (auto ship = std::dynamic_pointer_cast<const Ship>(candidate))
8100             fleet = local_context.ContextObjects().get<Fleet>(ship->FleetID());
8101 
8102     if (fleet) {
8103         // if a fleet is available, it is "moving", or not stationary, if it's
8104         // next system is a system and isn't the current system.  This will
8105         // mean fleets that have arrived at a system on the current turn will
8106         // be stationary, but fleets departing won't be stationary.
8107         int next_id = fleet->NextSystemID();
8108         int cur_id = fleet->SystemID();
8109         if (next_id != INVALID_OBJECT_ID && next_id != cur_id)
8110             return false;
8111     }
8112 
8113     return true;
8114 }
8115 
GetCheckSum() const8116 unsigned int Stationary::GetCheckSum() const {
8117     unsigned int retval{0};
8118 
8119     CheckSums::CheckSumCombine(retval, "Condition::Stationary");
8120 
8121     TraceLogger() << "GetCheckSum(Stationary): retval: " << retval;
8122     return retval;
8123 }
8124 
8125 ///////////////////////////////////////////////////////////
8126 // Aggressive                                            //
8127 ///////////////////////////////////////////////////////////
Aggressive()8128 Aggressive::Aggressive() :
8129     Aggressive(true)
8130 {}
8131 
Aggressive(bool aggressive)8132 Aggressive::Aggressive(bool aggressive) :
8133     Condition(),
8134     m_aggressive(aggressive)
8135 {
8136     m_root_candidate_invariant = true;
8137     m_target_invariant = true;
8138     m_source_invariant = true;
8139 }
8140 
operator ==(const Condition & rhs) const8141 bool Aggressive::operator==(const Condition& rhs) const
8142 { return Condition::operator==(rhs); }
8143 
Description(bool negated) const8144 std::string Aggressive::Description(bool negated/* = false*/) const {
8145     if (m_aggressive)
8146         return (!negated)
8147             ? UserString("DESC_AGGRESSIVE")
8148             : UserString("DESC_AGGRESSIVE_NOT");
8149     else
8150         return (!negated)
8151             ? UserString("DESC_PASSIVE")
8152             : UserString("DESC_PASSIVE_NOT");
8153 }
8154 
Dump(unsigned short ntabs) const8155 std::string Aggressive::Dump(unsigned short ntabs) const
8156 { return DumpIndent(ntabs) + (m_aggressive ? "Aggressive\n" : "Passive\n"); }
8157 
Match(const ScriptingContext & local_context) const8158 bool Aggressive::Match(const ScriptingContext& local_context) const {
8159     auto candidate = local_context.condition_local_candidate;
8160     if (!candidate) {
8161         ErrorLogger() << "Aggressive::Match passed no candidate object";
8162         return false;
8163     }
8164 
8165     // the only objects that can be aggressive are fleets and the ships in them.
8166     // so, attempt to cast the candidate object to a fleet or ship, and if it's
8167     // a ship get the fleet of that ship
8168     auto fleet = std::dynamic_pointer_cast<const Fleet>(candidate);
8169     if (!fleet)
8170         if (auto ship = std::dynamic_pointer_cast<const Ship>(candidate))
8171             fleet = local_context.ContextObjects().get<Fleet>(ship->FleetID());
8172 
8173     if (!fleet)
8174         return false;
8175 
8176     return m_aggressive == fleet->Aggressive();
8177 }
8178 
GetCheckSum() const8179 unsigned int Aggressive::GetCheckSum() const {
8180     unsigned int retval{0};
8181 
8182     CheckSums::CheckSumCombine(retval, "Condition::Aggressive");
8183     CheckSums::CheckSumCombine(retval, m_aggressive);
8184 
8185     TraceLogger() << "GetCheckSum(Aggressive): retval: " << retval;
8186     return retval;
8187 }
8188 
8189 ///////////////////////////////////////////////////////////
8190 // FleetSupplyableByEmpire                               //
8191 ///////////////////////////////////////////////////////////
FleetSupplyableByEmpire(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id)8192 FleetSupplyableByEmpire::FleetSupplyableByEmpire(std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id) :
8193     Condition(),
8194     m_empire_id(std::move(empire_id))
8195 {
8196     m_root_candidate_invariant = m_empire_id->RootCandidateInvariant();
8197     m_target_invariant = m_empire_id->TargetInvariant();
8198     m_source_invariant = m_empire_id->SourceInvariant();
8199 }
8200 
operator ==(const Condition & rhs) const8201 bool FleetSupplyableByEmpire::operator==(const Condition& rhs) const {
8202     if (this == &rhs)
8203         return true;
8204     if (typeid(*this) != typeid(rhs))
8205         return false;
8206 
8207     const FleetSupplyableByEmpire& rhs_ = static_cast<const FleetSupplyableByEmpire&>(rhs);
8208 
8209     CHECK_COND_VREF_MEMBER(m_empire_id)
8210 
8211     return true;
8212 }
8213 
8214 namespace {
8215     struct FleetSupplyableSimpleMatch {
FleetSupplyableSimpleMatchCondition::__anone9a56faf6911::FleetSupplyableSimpleMatch8216         FleetSupplyableSimpleMatch(int empire_id) :
8217             m_empire_id(empire_id)
8218         {}
8219 
operator ()Condition::__anone9a56faf6911::FleetSupplyableSimpleMatch8220         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
8221             if (!candidate)
8222                 return false;
8223 
8224             const Empire* empire = GetEmpire(m_empire_id);
8225             if (!empire)
8226                 return false;
8227 
8228             const SupplyManager& supply = GetSupplyManager();
8229             const auto& empire_supplyable_systems = supply.FleetSupplyableSystemIDs();
8230             auto it = empire_supplyable_systems.find(m_empire_id);
8231             if (it == empire_supplyable_systems.end())
8232                 return false;
8233             return it->second.count(candidate->SystemID());
8234         }
8235 
8236         int m_empire_id;
8237     };
8238 }
8239 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const8240 void FleetSupplyableByEmpire::Eval(const ScriptingContext& parent_context,
8241                                    ObjectSet& matches, ObjectSet& non_matches,
8242                                    SearchDomain search_domain/* = NON_MATCHES*/) const
8243 {
8244     bool simple_eval_safe = m_empire_id->ConstantExpr() ||
8245                             (m_empire_id->LocalCandidateInvariant() &&
8246                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
8247     if (simple_eval_safe) {
8248         // evaluate empire id once, and use to check all candidate objects
8249         int empire_id = m_empire_id->Eval(parent_context);
8250         EvalImpl(matches, non_matches, search_domain, FleetSupplyableSimpleMatch(empire_id));
8251     } else {
8252         // re-evaluate empire id for each candidate object
8253         Condition::Eval(parent_context, matches, non_matches, search_domain);
8254     }
8255 }
8256 
Description(bool negated) const8257 std::string FleetSupplyableByEmpire::Description(bool negated/* = false*/) const {
8258     std::string empire_str;
8259     if (m_empire_id) {
8260         int empire_id = ALL_EMPIRES;
8261         if (m_empire_id->ConstantExpr())
8262             empire_id = m_empire_id->Eval();
8263         if (const Empire* empire = GetEmpire(empire_id))
8264             empire_str = empire->Name();
8265         else
8266             empire_str = m_empire_id->Description();
8267     }
8268 
8269     return str(FlexibleFormat((!negated)
8270                ? UserString("DESC_SUPPLY_CONNECTED_FLEET")
8271                : UserString("DESC_SUPPLY_CONNECTED_FLEET_NOT"))
8272                % empire_str);
8273 }
8274 
Dump(unsigned short ntabs) const8275 std::string FleetSupplyableByEmpire::Dump(unsigned short ntabs) const
8276 { return DumpIndent(ntabs) + "ResupplyableBy empire_id = " + m_empire_id->Dump(ntabs); }
8277 
Match(const ScriptingContext & local_context) const8278 bool FleetSupplyableByEmpire::Match(const ScriptingContext& local_context) const {
8279     auto candidate = local_context.condition_local_candidate;
8280     if (!candidate) {
8281         ErrorLogger() << "FleetSupplyableByEmpire::Match passed no candidate object";
8282         return false;
8283     }
8284 
8285     int empire_id = m_empire_id->Eval(local_context);
8286 
8287     return FleetSupplyableSimpleMatch(empire_id)(candidate);
8288 }
8289 
SetTopLevelContent(const std::string & content_name)8290 void FleetSupplyableByEmpire::SetTopLevelContent(const std::string& content_name) {
8291     if (m_empire_id)
8292         m_empire_id->SetTopLevelContent(content_name);
8293 }
8294 
GetCheckSum() const8295 unsigned int FleetSupplyableByEmpire::GetCheckSum() const {
8296     unsigned int retval{0};
8297 
8298     CheckSums::CheckSumCombine(retval, "Condition::FleetSupplyableByEmpire");
8299     CheckSums::CheckSumCombine(retval, m_empire_id);
8300 
8301     TraceLogger() << "GetCheckSum(FleetSupplyableByEmpire): retval: " << retval;
8302     return retval;
8303 }
8304 
8305 ///////////////////////////////////////////////////////////
8306 // ResourceSupplyConnectedByEmpire                       //
8307 ///////////////////////////////////////////////////////////
ResourceSupplyConnectedByEmpire(std::unique_ptr<ValueRef::ValueRef<int>> && empire_id,std::unique_ptr<Condition> && condition)8308 ResourceSupplyConnectedByEmpire::ResourceSupplyConnectedByEmpire(
8309     std::unique_ptr<ValueRef::ValueRef<int>>&& empire_id,
8310     std::unique_ptr<Condition>&& condition) :
8311     Condition(),
8312     m_empire_id(std::move(empire_id)),
8313     m_condition(std::move(condition))
8314 {
8315     m_root_candidate_invariant = m_empire_id->RootCandidateInvariant() && m_condition->RootCandidateInvariant();
8316     m_target_invariant = m_empire_id->TargetInvariant() && m_condition->TargetInvariant();
8317     m_source_invariant = m_empire_id->SourceInvariant() && m_condition->SourceInvariant();
8318 }
8319 
operator ==(const Condition & rhs) const8320 bool ResourceSupplyConnectedByEmpire::operator==(const Condition& rhs) const {
8321     if (this == &rhs)
8322         return true;
8323     if (typeid(*this) != typeid(rhs))
8324         return false;
8325 
8326     const ResourceSupplyConnectedByEmpire& rhs_ = static_cast<const ResourceSupplyConnectedByEmpire&>(rhs);
8327 
8328     CHECK_COND_VREF_MEMBER(m_empire_id)
8329 
8330     return true;
8331 }
8332 
8333 namespace {
8334     struct ResourceSupplySimpleMatch {
ResourceSupplySimpleMatchCondition::__anone9a56faf6a11::ResourceSupplySimpleMatch8335         ResourceSupplySimpleMatch(int empire_id, const ObjectSet& from_objects, const ObjectMap& objects) :
8336             m_empire_id(empire_id),
8337             m_from_objects(from_objects),
8338             m_objects(objects)
8339         {}
8340 
operator ()Condition::__anone9a56faf6a11::ResourceSupplySimpleMatch8341         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
8342             if (!candidate)
8343                 return false;
8344             if (m_from_objects.empty())
8345                 return false;
8346             const auto& groups = GetSupplyManager().ResourceSupplyGroups(m_empire_id);
8347             if (groups.empty())
8348                 return false;
8349 
8350             // is candidate object connected to a subcondition matching object by resource supply?
8351             // first check if candidate object is (or is a building on) a blockaded planet
8352             // "isolated" objects are anything not in a non-blockaded system
8353             bool is_isolated = true;
8354             for (const auto& group : groups) {
8355                 if (group.count(candidate->SystemID())) {
8356                     is_isolated = false;
8357                     break;
8358                 }
8359             }
8360             if (is_isolated) {
8361                 // planets are still supply-connected to themselves even if blockaded
8362                 auto candidate_planet = std::dynamic_pointer_cast<const Planet>(candidate);
8363                 std::shared_ptr<const ::Building> building;
8364                 if (!candidate_planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate)))
8365                     candidate_planet = m_objects.get<Planet>(building->PlanetID());
8366                 if (candidate_planet) {
8367                     int candidate_planet_id = candidate_planet->ID();
8368                     // can only match if the from_object is (or is on) the same planet
8369                     for (auto& from_object : m_from_objects) {
8370                         auto from_obj_planet = std::dynamic_pointer_cast<const Planet>(from_object);
8371                         std::shared_ptr<const ::Building> from_building;
8372                         if (!from_obj_planet && (from_building = std::dynamic_pointer_cast<const ::Building>(from_object)))
8373                             from_obj_planet = m_objects.get<Planet>(from_building->PlanetID());
8374                         if (from_obj_planet && from_obj_planet->ID() == candidate_planet_id)
8375                             return true;
8376                     }
8377                 }
8378                 // candidate is isolated, but did not match planet for any test object
8379                 return false;
8380             }
8381             // candidate is not blockaded, so check for system group matches
8382             for (auto& from_object : m_from_objects) {
8383                 for (const auto& group : groups) {
8384                     if (group.count(from_object->SystemID())) {
8385                         // found resource sharing group containing test object system.  Does it also contain candidate?
8386                         if (group.count(candidate->SystemID()))
8387                             return true;    // test object and candidate object are in same resourse sharing group
8388                         else
8389                             // test object is not in resource sharing group with candidate
8390                             // as each object can be in only one group, no need to check the remaining groups
8391                             break;
8392                     }
8393                     // current subcondition-matching object is not in this resource sharing group
8394                 }
8395                 // current subcondition-matching object is not in any resource sharing group for this empire
8396             }
8397 
8398             return false;
8399         }
8400 
8401         int m_empire_id;
8402         const ObjectSet& m_from_objects;
8403         const ObjectMap& m_objects;
8404     };
8405 }
8406 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const8407 void ResourceSupplyConnectedByEmpire::Eval(const ScriptingContext& parent_context,
8408                                            ObjectSet& matches, ObjectSet& non_matches,
8409                                            SearchDomain search_domain/* = NON_MATCHES*/) const
8410 {
8411     bool simple_eval_safe = m_empire_id->ConstantExpr() ||
8412                             (m_empire_id->LocalCandidateInvariant() &&
8413                             (parent_context.condition_root_candidate || RootCandidateInvariant()));
8414     if (simple_eval_safe) {
8415         // evaluate contained objects once and check for all candidates
8416 
8417         // get objects to be considering for matching against subcondition
8418         ObjectSet subcondition_matches;
8419         m_condition->Eval(parent_context, subcondition_matches);
8420         int empire_id = m_empire_id->Eval(parent_context);
8421 
8422         EvalImpl(matches, non_matches, search_domain, ResourceSupplySimpleMatch(empire_id, subcondition_matches, parent_context.ContextObjects()));
8423     } else {
8424         // re-evaluate empire id for each candidate object
8425         Condition::Eval(parent_context, matches, non_matches, search_domain);
8426     }
8427 }
8428 
Match(const ScriptingContext & local_context) const8429 bool ResourceSupplyConnectedByEmpire::Match(const ScriptingContext& local_context) const {
8430     auto candidate = local_context.condition_local_candidate;
8431     if (!candidate) {
8432         ErrorLogger() << "ResourceSupplyConnectedByEmpire::Match passed no candidate object";
8433         return false;
8434     }
8435 
8436     // get subcondition matches
8437     ObjectSet subcondition_matches;
8438     m_condition->Eval(local_context, subcondition_matches);
8439     int empire_id = m_empire_id->Eval(local_context);
8440 
8441     return ResourceSupplySimpleMatch(empire_id, subcondition_matches, local_context.ContextObjects())(candidate);
8442 }
8443 
Description(bool negated) const8444 std::string ResourceSupplyConnectedByEmpire::Description(bool negated/* = false*/) const {
8445     std::string empire_str;
8446     if (m_empire_id) {
8447         int empire_id = ALL_EMPIRES;
8448         if (m_empire_id->ConstantExpr())
8449             empire_id = m_empire_id->Eval();
8450         if (const Empire* empire = GetEmpire(empire_id))
8451             empire_str = empire->Name();
8452         else
8453             empire_str = m_empire_id->Description();
8454     }
8455 
8456     return str(FlexibleFormat((!negated)
8457                ? UserString("DESC_SUPPLY_CONNECTED_RESOURCE")
8458                : UserString("DESC_SUPPLY_CONNECTED_RESOURCE_NOT"))
8459                % empire_str
8460                % m_condition->Description());
8461 }
8462 
Dump(unsigned short ntabs) const8463 std::string ResourceSupplyConnectedByEmpire::Dump(unsigned short ntabs) const {
8464     std::string retval = DumpIndent(ntabs) + "ResourceSupplyConnectedBy empire_id = "
8465         + m_empire_id->Dump(ntabs) + " condition = \n";
8466     retval += m_condition->Dump(ntabs+1);
8467     return retval;
8468 }
8469 
SetTopLevelContent(const std::string & content_name)8470 void ResourceSupplyConnectedByEmpire::SetTopLevelContent(const std::string& content_name) {
8471     if (m_empire_id)
8472         m_empire_id->SetTopLevelContent(content_name);
8473     if (m_condition)
8474         m_condition->SetTopLevelContent(content_name);
8475 }
8476 
GetCheckSum() const8477 unsigned int ResourceSupplyConnectedByEmpire::GetCheckSum() const {
8478     unsigned int retval{0};
8479 
8480     CheckSums::CheckSumCombine(retval, "Condition::ResourceSupplyConnectedByEmpire");
8481     CheckSums::CheckSumCombine(retval, m_empire_id);
8482     CheckSums::CheckSumCombine(retval, m_condition);
8483 
8484     TraceLogger() << "GetCheckSum(ResourceSupplyConnectedByEmpire): retval: " << retval;
8485     return retval;
8486 }
8487 
8488 ///////////////////////////////////////////////////////////
8489 // CanColonize                                           //
8490 ///////////////////////////////////////////////////////////
CanColonize()8491 CanColonize::CanColonize() :
8492     Condition()
8493 {
8494     m_root_candidate_invariant = true;
8495     m_target_invariant = true;
8496     m_source_invariant = true;
8497 }
8498 
operator ==(const Condition & rhs) const8499 bool CanColonize::operator==(const Condition& rhs) const
8500 { return Condition::operator==(rhs); }
8501 
Description(bool negated) const8502 std::string CanColonize::Description(bool negated/* = false*/) const {
8503     return str(FlexibleFormat((!negated)
8504         ? UserString("DESC_CAN_COLONIZE")
8505         : UserString("DESC_CAN_COLONIZE_NOT")));
8506 }
8507 
Dump(unsigned short ntabs) const8508 std::string CanColonize::Dump(unsigned short ntabs) const
8509 { return DumpIndent(ntabs) + "CanColonize\n"; }
8510 
Match(const ScriptingContext & local_context) const8511 bool CanColonize::Match(const ScriptingContext& local_context) const {
8512     auto candidate = local_context.condition_local_candidate;
8513     if (!candidate) {
8514         ErrorLogger() << "CanColonize::Match passed no candidate object";
8515         return false;
8516     }
8517 
8518     // is it a ship, a planet, or a building on a planet?
8519     std::string species_name;
8520     if (candidate->ObjectType() == OBJ_PLANET) {
8521         auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
8522         if (!planet) {
8523             ErrorLogger() << "CanColonize couldn't cast supposedly planet candidate";
8524             return false;
8525         }
8526         species_name = planet->SpeciesName();
8527 
8528     } else if (candidate->ObjectType() == OBJ_BUILDING) {
8529         auto building = std::dynamic_pointer_cast<const ::Building>(candidate);
8530         if (!building) {
8531             ErrorLogger() << "CanColonize couldn't cast supposedly building candidate";
8532             return false;
8533         }
8534         auto planet = local_context.ContextObjects().get<Planet>(building->PlanetID());
8535         if (!planet) {
8536             ErrorLogger() << "CanColonize couldn't get building's planet";
8537             return false;
8538         }
8539         species_name = planet->SpeciesName();
8540 
8541     } else if (candidate->ObjectType() == OBJ_SHIP) {
8542         auto ship = std::dynamic_pointer_cast<const Ship>(candidate);
8543         if (!ship) {
8544             ErrorLogger() << "CanColonize couldn't cast supposedly ship candidate";
8545             return false;
8546         }
8547         species_name = ship->SpeciesName();
8548     }
8549 
8550     if (species_name.empty())
8551         return false;
8552     auto species = GetSpecies(species_name);
8553     if (!species) {
8554         ErrorLogger() << "CanColonize couldn't get species: " << species_name;
8555         return false;
8556     }
8557     return species->CanColonize();
8558 }
8559 
GetCheckSum() const8560 unsigned int CanColonize::GetCheckSum() const {
8561     unsigned int retval{0};
8562 
8563     CheckSums::CheckSumCombine(retval, "Condition::CanColonize");
8564 
8565     TraceLogger() << "GetCheckSum(CanColonize): retval: " << retval;
8566     return retval;
8567 }
8568 
8569 ///////////////////////////////////////////////////////////
8570 // CanProduceShips                                       //
8571 ///////////////////////////////////////////////////////////
CanProduceShips()8572 CanProduceShips::CanProduceShips() :
8573     Condition()
8574 {
8575     m_root_candidate_invariant = true;
8576     m_target_invariant = true;
8577     m_source_invariant = true;
8578 }
8579 
operator ==(const Condition & rhs) const8580 bool CanProduceShips::operator==(const Condition& rhs) const
8581 { return Condition::operator==(rhs); }
8582 
Description(bool negated) const8583 std::string CanProduceShips::Description(bool negated/* = false*/) const {
8584     return str(FlexibleFormat((!negated)
8585         ? UserString("DESC_CAN_PRODUCE_SHIPS")
8586         : UserString("DESC_CAN_PRODUCE_SHIPS_NOT")));
8587 }
8588 
Dump(unsigned short ntabs) const8589 std::string CanProduceShips::Dump(unsigned short ntabs) const
8590 { return DumpIndent(ntabs) + "CanColonize\n"; }
8591 
Match(const ScriptingContext & local_context) const8592 bool CanProduceShips::Match(const ScriptingContext& local_context) const {
8593     auto candidate = local_context.condition_local_candidate;
8594     if (!candidate) {
8595         ErrorLogger() << "CanProduceShips::Match passed no candidate object";
8596         return false;
8597     }
8598 
8599     // is it a ship, a planet, or a building on a planet?
8600     std::string species_name;
8601     if (candidate->ObjectType() == OBJ_PLANET) {
8602         auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
8603         if (!planet) {
8604             ErrorLogger() << "CanProduceShips couldn't cast supposedly planet candidate";
8605             return false;
8606         }
8607         species_name = planet->SpeciesName();
8608 
8609     } else if (candidate->ObjectType() == OBJ_BUILDING) {
8610         auto building = std::dynamic_pointer_cast<const ::Building>(candidate);
8611         if (!building) {
8612             ErrorLogger() << "CanProduceShips couldn't cast supposedly building candidate";
8613             return false;
8614         }
8615         auto planet = local_context.ContextObjects().get<Planet>(building->PlanetID());
8616         if (!planet) {
8617             ErrorLogger() << "CanProduceShips couldn't get building's planet";
8618             return false;
8619         }
8620         species_name = planet->SpeciesName();
8621 
8622     } else if (candidate->ObjectType() == OBJ_SHIP) {
8623         auto ship = std::dynamic_pointer_cast<const Ship>(candidate);
8624         if (!ship) {
8625             ErrorLogger() << "CanProduceShips couldn't cast supposedly ship candidate";
8626             return false;
8627         }
8628         species_name = ship->SpeciesName();
8629     }
8630 
8631     if (species_name.empty())
8632         return false;
8633     auto species = GetSpecies(species_name);
8634     if (!species) {
8635         ErrorLogger() << "CanProduceShips couldn't get species: " << species_name;
8636         return false;
8637     }
8638     return species->CanProduceShips();
8639 }
8640 
GetCheckSum() const8641 unsigned int CanProduceShips::GetCheckSum() const {
8642     unsigned int retval{0};
8643 
8644     CheckSums::CheckSumCombine(retval, "Condition::CanProduceShips");
8645 
8646     TraceLogger() << "GetCheckSum(CanProduceShips): retval: " << retval;
8647     return retval;
8648 }
8649 
8650 ///////////////////////////////////////////////////////////
8651 // OrderedBombarded                                      //
8652 ///////////////////////////////////////////////////////////
OrderedBombarded(std::unique_ptr<Condition> && by_object_condition)8653 OrderedBombarded::OrderedBombarded(std::unique_ptr<Condition>&& by_object_condition) :
8654     Condition(),
8655     m_by_object_condition(std::move(by_object_condition))
8656 {
8657     m_root_candidate_invariant = m_by_object_condition->RootCandidateInvariant();
8658     m_target_invariant = m_by_object_condition->TargetInvariant();
8659     m_source_invariant = m_by_object_condition->SourceInvariant();
8660 }
8661 
operator ==(const Condition & rhs) const8662 bool OrderedBombarded::operator==(const Condition& rhs) const {
8663     if (this == &rhs)
8664         return true;
8665     if (typeid(*this) != typeid(rhs))
8666         return false;
8667 
8668     const OrderedBombarded& rhs_ = static_cast<const OrderedBombarded&>(rhs);
8669 
8670     CHECK_COND_VREF_MEMBER(m_by_object_condition)
8671 
8672     return true;
8673 }
8674 
8675 namespace {
8676     struct OrderedBombardedSimpleMatch {
OrderedBombardedSimpleMatchCondition::__anone9a56faf6b11::OrderedBombardedSimpleMatch8677         OrderedBombardedSimpleMatch(const ObjectSet& by_objects) :
8678             m_by_objects(by_objects)
8679         {}
8680 
operator ()Condition::__anone9a56faf6b11::OrderedBombardedSimpleMatch8681         bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
8682             if (!candidate)
8683                 return false;
8684             if (m_by_objects.empty())
8685                 return false;
8686             auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
8687             if (!planet)
8688                 return false;
8689             int planet_id = planet->ID();
8690             if (planet_id == INVALID_OBJECT_ID)
8691                 return false;
8692 
8693             // check if any of the by_objects is ordered to bombard the candidate planet
8694             for (auto& obj : m_by_objects) {
8695                 auto ship = std::dynamic_pointer_cast<const Ship>(obj);
8696                 if (!ship)
8697                     continue;
8698                 if (ship->OrderedBombardPlanet() == planet_id)
8699                     return true;
8700             }
8701             return false;
8702         }
8703 
8704         const ObjectSet& m_by_objects;
8705     };
8706 }
8707 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const8708 void OrderedBombarded::Eval(const ScriptingContext& parent_context,
8709                             ObjectSet& matches, ObjectSet& non_matches,
8710                             SearchDomain search_domain/* = NON_MATCHES*/) const
8711 {
8712     bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
8713     if (simple_eval_safe) {
8714         // evaluate contained objects once and check for all candidates
8715 
8716         // get subcondition matches
8717         ObjectSet subcondition_matches;
8718         m_by_object_condition->Eval(parent_context, subcondition_matches);
8719 
8720         EvalImpl(matches, non_matches, search_domain, OrderedBombardedSimpleMatch(subcondition_matches));
8721     } else {
8722         // re-evaluate contained objects for each candidate object
8723         Condition::Eval(parent_context, matches, non_matches, search_domain);
8724     }
8725 }
8726 
Description(bool negated) const8727 std::string OrderedBombarded::Description(bool negated/* = false*/) const {
8728     std::string by_str;
8729     if (m_by_object_condition)
8730         by_str = m_by_object_condition->Description();
8731 
8732     return str(FlexibleFormat((!negated)
8733                ? UserString("DESC_ORDERED_BOMBARDED")
8734                : UserString("DESC_ORDERED_BOMBARDED_NOT"))
8735                % by_str);
8736 }
8737 
Dump(unsigned short ntabs) const8738 std::string OrderedBombarded::Dump(unsigned short ntabs) const
8739 { return DumpIndent(ntabs) + "OrderedBombarded by_object = " + m_by_object_condition->Dump(ntabs); }
8740 
Match(const ScriptingContext & local_context) const8741 bool OrderedBombarded::Match(const ScriptingContext& local_context) const {
8742     auto candidate = local_context.condition_local_candidate;
8743     if (!candidate) {
8744         ErrorLogger() << "OrderedBombarded::Match passed no candidate object";
8745         return false;
8746     }
8747 
8748     // get subcondition matches
8749     ObjectSet subcondition_matches;
8750     m_by_object_condition->Eval(local_context, subcondition_matches);
8751 
8752     return OrderedBombardedSimpleMatch(subcondition_matches)(candidate);
8753 }
8754 
SetTopLevelContent(const std::string & content_name)8755 void OrderedBombarded::SetTopLevelContent(const std::string& content_name) {
8756     if (m_by_object_condition)
8757         m_by_object_condition->SetTopLevelContent(content_name);
8758 }
8759 
GetCheckSum() const8760 unsigned int OrderedBombarded::GetCheckSum() const {
8761     unsigned int retval{0};
8762 
8763     CheckSums::CheckSumCombine(retval, "Condition::OrderedBombarded");
8764     CheckSums::CheckSumCombine(retval, m_by_object_condition);
8765 
8766     TraceLogger() << "GetCheckSum(OrderedBombarded): retval: " << retval;
8767     return retval;
8768 }
8769 
8770 ///////////////////////////////////////////////////////////
8771 // ValueTest                                             //
8772 ///////////////////////////////////////////////////////////
8773 namespace {
Comparison(float val1,ComparisonType comp,float val2)8774     bool Comparison(float val1, ComparisonType comp, float val2) {
8775         switch (comp) {
8776             case EQUAL:                 return val1 == val2;
8777             case GREATER_THAN:          return val1 > val2;
8778             case GREATER_THAN_OR_EQUAL: return val1 >= val2;
8779             case LESS_THAN:             return val1 < val2;
8780             case LESS_THAN_OR_EQUAL:    return val1 <= val2;
8781             case NOT_EQUAL:             return val1 != val2;
8782             case INVALID_COMPARISON:
8783             default:                    return false;
8784         }
8785     }
Comparison(const std::string & val1,ComparisonType comp,const std::string & val2)8786     bool Comparison(const std::string& val1, ComparisonType comp,
8787                     const std::string& val2)
8788     {
8789         switch (comp) {
8790             case EQUAL:                 return val1 == val2;
8791             case NOT_EQUAL:             return val1 != val2;
8792             case INVALID_COMPARISON:
8793             default:                    return false;
8794         }
8795     }
8796 
CompareTypeString(ComparisonType comp)8797     std::string CompareTypeString(ComparisonType comp) {
8798         switch (comp) {
8799         case EQUAL:                 return "=";
8800         case GREATER_THAN:          return ">";
8801         case GREATER_THAN_OR_EQUAL: return ">=";
8802         case LESS_THAN:             return "<";
8803         case LESS_THAN_OR_EQUAL:    return "<=";
8804         case NOT_EQUAL:             return "!=";
8805         case INVALID_COMPARISON:
8806         default:                    return "";
8807         }
8808     }
8809 }
8810 
ValueTest(std::unique_ptr<ValueRef::ValueRef<double>> && value_ref1,ComparisonType comp1,std::unique_ptr<ValueRef::ValueRef<double>> && value_ref2,ComparisonType comp2,std::unique_ptr<ValueRef::ValueRef<double>> && value_ref3)8811 ValueTest::ValueTest(std::unique_ptr<ValueRef::ValueRef<double>>&& value_ref1,
8812                      ComparisonType comp1,
8813                      std::unique_ptr<ValueRef::ValueRef<double>>&& value_ref2,
8814                      ComparisonType comp2,
8815                      std::unique_ptr<ValueRef::ValueRef<double>>&& value_ref3) :
8816     Condition(),
8817     m_value_ref1(std::move(value_ref1)),
8818     m_value_ref2(std::move(value_ref2)),
8819     m_value_ref3(std::move(value_ref3)),
8820     m_compare_type1(comp1),
8821     m_compare_type2(comp2)
8822 {
8823     auto operands = {m_value_ref1.get(), m_value_ref2.get(), m_value_ref3.get()};
8824     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
8825     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
8826     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
8827 }
8828 
ValueTest(std::unique_ptr<ValueRef::ValueRef<std::string>> && value_ref1,ComparisonType comp1,std::unique_ptr<ValueRef::ValueRef<std::string>> && value_ref2,ComparisonType comp2,std::unique_ptr<ValueRef::ValueRef<std::string>> && value_ref3)8829 ValueTest::ValueTest(std::unique_ptr<ValueRef::ValueRef<std::string>>&& value_ref1,
8830                      ComparisonType comp1,
8831                      std::unique_ptr<ValueRef::ValueRef<std::string>>&& value_ref2,
8832                      ComparisonType comp2,
8833                      std::unique_ptr<ValueRef::ValueRef<std::string>>&& value_ref3) :
8834     Condition(),
8835     m_string_value_ref1(std::move(value_ref1)),
8836     m_string_value_ref2(std::move(value_ref2)),
8837     m_string_value_ref3(std::move(value_ref3)),
8838     m_compare_type1(comp1),
8839     m_compare_type2(comp2)
8840 {
8841     auto operands = {m_string_value_ref1.get(), m_string_value_ref2.get(), m_string_value_ref3.get()};
8842     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
8843     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
8844     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
8845 }
8846 
ValueTest(std::unique_ptr<ValueRef::ValueRef<int>> && value_ref1,ComparisonType comp1,std::unique_ptr<ValueRef::ValueRef<int>> && value_ref2,ComparisonType comp2,std::unique_ptr<ValueRef::ValueRef<int>> && value_ref3)8847 ValueTest::ValueTest(std::unique_ptr<ValueRef::ValueRef<int>>&& value_ref1,
8848                      ComparisonType comp1,
8849                      std::unique_ptr<ValueRef::ValueRef<int>>&& value_ref2,
8850                      ComparisonType comp2,
8851                      std::unique_ptr<ValueRef::ValueRef<int>>&& value_ref3) :
8852     Condition(),
8853     m_int_value_ref1(std::move(value_ref1)),
8854     m_int_value_ref2(std::move(value_ref2)),
8855     m_int_value_ref3(std::move(value_ref3)),
8856     m_compare_type1(comp1),
8857     m_compare_type2(comp2)
8858 {
8859     auto operands = {m_int_value_ref1.get(), m_int_value_ref2.get(), m_int_value_ref3.get()};
8860     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
8861     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
8862     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
8863 }
8864 
operator ==(const Condition & rhs) const8865 bool ValueTest::operator==(const Condition& rhs) const {
8866     if (this == &rhs)
8867         return true;
8868     if (typeid(*this) != typeid(rhs))
8869         return false;
8870 
8871     const ValueTest& rhs_ = static_cast<const ValueTest&>(rhs);
8872 
8873     CHECK_COND_VREF_MEMBER(m_value_ref1)
8874     CHECK_COND_VREF_MEMBER(m_value_ref2)
8875     CHECK_COND_VREF_MEMBER(m_value_ref3)
8876     CHECK_COND_VREF_MEMBER(m_string_value_ref1)
8877     CHECK_COND_VREF_MEMBER(m_string_value_ref2)
8878     CHECK_COND_VREF_MEMBER(m_string_value_ref3)
8879     CHECK_COND_VREF_MEMBER(m_int_value_ref1)
8880     CHECK_COND_VREF_MEMBER(m_int_value_ref2)
8881     CHECK_COND_VREF_MEMBER(m_int_value_ref3)
8882 
8883     if (m_compare_type1 != rhs_.m_compare_type1)
8884         return false;
8885     if (m_compare_type2 != rhs_.m_compare_type2)
8886         return false;
8887 
8888     return true;
8889 }
8890 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const8891 void ValueTest::Eval(const ScriptingContext& parent_context,
8892                      ObjectSet& matches, ObjectSet& non_matches,
8893                      SearchDomain search_domain/* = NON_MATCHES*/) const
8894 {
8895     bool simple_eval_safe = ((!m_value_ref1         || m_value_ref1->LocalCandidateInvariant()) &&
8896                              (!m_value_ref2         || m_value_ref2->LocalCandidateInvariant()) &&
8897                              (!m_value_ref3         || m_value_ref3->LocalCandidateInvariant()) &&
8898                              (!m_string_value_ref1  || m_string_value_ref1->LocalCandidateInvariant()) &&
8899                              (!m_string_value_ref2  || m_string_value_ref2->LocalCandidateInvariant()) &&
8900                              (!m_string_value_ref3  || m_string_value_ref3->LocalCandidateInvariant()) &&
8901                              (!m_int_value_ref1     || m_int_value_ref1->LocalCandidateInvariant()) &&
8902                              (!m_int_value_ref2     || m_int_value_ref2->LocalCandidateInvariant()) &&
8903                              (!m_int_value_ref3     || m_int_value_ref3->LocalCandidateInvariant()) &&
8904                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
8905 
8906     if (simple_eval_safe) {
8907         // evaluate value and range limits once, use to match all candidates
8908         bool passed = Match(parent_context);
8909 
8910         // transfer objects to or from candidate set, according to whether the value comparisons were true
8911         if (search_domain == MATCHES && !passed) {
8912             non_matches.insert(non_matches.end(), matches.begin(), matches.end());
8913             matches.clear();
8914         }
8915         if (search_domain == NON_MATCHES && passed) {
8916             matches.insert(matches.end(), non_matches.begin(), non_matches.end());
8917             non_matches.clear();
8918         }
8919 
8920     } else {
8921         // re-evaluate value and ranges for each candidate object
8922         Condition::Eval(parent_context, matches, non_matches, search_domain);
8923     }
8924 }
8925 
Description(bool negated) const8926 std::string ValueTest::Description(bool negated/* = false*/) const {
8927     std::string value_str1, value_str2, value_str3;
8928     if (m_value_ref1)
8929         value_str1 = m_value_ref1->Description();
8930     else if (m_string_value_ref1)
8931         value_str1 = m_string_value_ref1->Description();
8932     else if (m_int_value_ref1)
8933         value_str1 = m_int_value_ref1->Description();
8934 
8935     if (m_value_ref2)
8936         value_str2 = m_value_ref2->Description();
8937     else if (m_string_value_ref2)
8938         value_str2 = m_string_value_ref2->Description();
8939     else if (m_int_value_ref2)
8940         value_str2 = m_int_value_ref2->Description();
8941 
8942     if (m_value_ref3)
8943         value_str3 = m_value_ref3->Description();
8944     else if (m_string_value_ref3)
8945         value_str3 = m_string_value_ref3->Description();
8946     else if (m_int_value_ref3)
8947         value_str3 = m_int_value_ref3->Description();
8948 
8949     std::string comp_str1 = CompareTypeString(m_compare_type1);
8950     std::string comp_str2 = CompareTypeString(m_compare_type2);
8951 
8952     std::string composed_comparison = value_str1 + " " + comp_str1 + " " + value_str2;
8953     if (!comp_str2.empty())
8954         composed_comparison += " " + comp_str2;
8955     if (!value_str3.empty())
8956         composed_comparison += +" " + value_str3;
8957 
8958     return str(FlexibleFormat((!negated)
8959                ? UserString("DESC_VALUE_TEST")
8960                : UserString("DESC_VALUE_TEST_NOT"))
8961                % composed_comparison);
8962 }
8963 
Dump(unsigned short ntabs) const8964 std::string ValueTest::Dump(unsigned short ntabs) const {
8965     std::string retval = DumpIndent(ntabs) + "(";
8966     if (m_value_ref1)
8967         retval += m_value_ref1->Dump(ntabs);
8968     else if (m_string_value_ref1)
8969         retval += m_string_value_ref1->Dump(ntabs);
8970     else if (m_int_value_ref1)
8971         retval += m_int_value_ref1->Dump(ntabs);
8972 
8973     if (m_compare_type1 != INVALID_COMPARISON)
8974         retval += " " + CompareTypeString(m_compare_type1);
8975 
8976     if (m_value_ref2)
8977         retval += " " + m_value_ref2->Dump(ntabs);
8978     else if (m_string_value_ref2)
8979         retval += " " + m_string_value_ref2->Dump(ntabs);
8980     else if (m_int_value_ref2)
8981         retval += " " + m_int_value_ref2->Dump(ntabs);
8982 
8983     if (m_compare_type2 != INVALID_COMPARISON)
8984         retval += " " + CompareTypeString(m_compare_type2);
8985 
8986     if (m_value_ref3)
8987         retval += " " + m_value_ref3->Dump(ntabs);
8988     else if (m_string_value_ref3)
8989         retval += " " + m_string_value_ref3->Dump(ntabs);
8990     else if (m_int_value_ref3)
8991         retval += " " + m_int_value_ref3->Dump(ntabs);
8992 
8993     retval += ")\n";
8994     return retval;
8995 }
8996 
Match(const ScriptingContext & local_context) const8997 bool ValueTest::Match(const ScriptingContext& local_context) const {
8998     if (m_compare_type1 == INVALID_COMPARISON)
8999         return false;
9000 
9001     // simple evaluation should have only local-candidate-invariation sub-value-refs
9002     // base class evalulation should have defined local candidate
9003 
9004     if (m_value_ref1) {
9005         if (!m_value_ref2)
9006             return false;
9007 
9008         float val1 = m_value_ref1->Eval(local_context);
9009         float val2 = m_value_ref2->Eval(local_context);
9010         if (!Comparison(val1, m_compare_type1, val2))
9011             return false;
9012 
9013         if (m_compare_type2 == INVALID_COMPARISON || !m_value_ref3)
9014             return true;
9015 
9016         float val3 = m_value_ref3->Eval(local_context);
9017         return Comparison(val2, m_compare_type1, val3);
9018 
9019     } else if (m_string_value_ref1) {
9020         if (!m_string_value_ref2)
9021             return false;
9022 
9023         std::string val1 = m_string_value_ref1->Eval(local_context);
9024         std::string val2 = m_string_value_ref2->Eval(local_context);
9025         if (!Comparison(val1, m_compare_type1, val2))
9026             return false;
9027 
9028         if (m_compare_type2 == INVALID_COMPARISON || !m_value_ref3)
9029             return true;
9030 
9031         std::string val3 = m_string_value_ref3->Eval(local_context);
9032         return Comparison(val2, m_compare_type1, val3);
9033 
9034     } else if (m_int_value_ref1) {
9035         if (!m_int_value_ref2)
9036             return false;
9037 
9038         int val1 = m_int_value_ref1->Eval(local_context);
9039         int val2 = m_int_value_ref2->Eval(local_context);
9040         if (!Comparison(val1, m_compare_type1, val2))
9041             return false;
9042 
9043         if (m_compare_type2 == INVALID_COMPARISON || !m_value_ref3)
9044             return true;
9045 
9046         int val3 = m_int_value_ref3->Eval(local_context);
9047         return Comparison(val2, m_compare_type1, val3);
9048     }
9049 
9050     return false;
9051 }
9052 
SetTopLevelContent(const std::string & content_name)9053 void ValueTest::SetTopLevelContent(const std::string& content_name) {
9054     if (m_value_ref1)
9055         m_value_ref1->SetTopLevelContent(content_name);
9056     if (m_value_ref2)
9057         m_value_ref2->SetTopLevelContent(content_name);
9058     if (m_value_ref3)
9059         m_value_ref3->SetTopLevelContent(content_name);
9060     if (m_string_value_ref1)
9061         m_string_value_ref1->SetTopLevelContent(content_name);
9062     if (m_string_value_ref2)
9063         m_string_value_ref2->SetTopLevelContent(content_name);
9064     if (m_string_value_ref3)
9065         m_string_value_ref3->SetTopLevelContent(content_name);
9066     if (m_int_value_ref1)
9067         m_int_value_ref1->SetTopLevelContent(content_name);
9068     if (m_int_value_ref2)
9069         m_int_value_ref2->SetTopLevelContent(content_name);
9070     if (m_int_value_ref3)
9071         m_int_value_ref3->SetTopLevelContent(content_name);
9072 }
9073 
GetCheckSum() const9074 unsigned int ValueTest::GetCheckSum() const {
9075     unsigned int retval{0};
9076 
9077     CheckSums::CheckSumCombine(retval, "Condition::ValueTest");
9078     CheckSums::CheckSumCombine(retval, m_value_ref1);
9079     CheckSums::CheckSumCombine(retval, m_value_ref2);
9080     CheckSums::CheckSumCombine(retval, m_value_ref3);
9081     CheckSums::CheckSumCombine(retval, m_string_value_ref1);
9082     CheckSums::CheckSumCombine(retval, m_string_value_ref2);
9083     CheckSums::CheckSumCombine(retval, m_string_value_ref3);
9084     CheckSums::CheckSumCombine(retval, m_int_value_ref1);
9085     CheckSums::CheckSumCombine(retval, m_int_value_ref2);
9086     CheckSums::CheckSumCombine(retval, m_int_value_ref3);
9087     CheckSums::CheckSumCombine(retval, m_compare_type1);
9088     CheckSums::CheckSumCombine(retval, m_compare_type2);
9089 
9090     TraceLogger() << "GetCheckSum(ValueTest): retval: " << retval;
9091     return retval;
9092 }
9093 
9094 ///////////////////////////////////////////////////////////
9095 // Location                                              //
9096 ///////////////////////////////////////////////////////////
9097 namespace {
GetLocationCondition(ContentType content_type,const std::string & name1,const std::string & name2)9098     const Condition* GetLocationCondition(ContentType content_type,
9099                                               const std::string& name1,
9100                                               const std::string& name2)
9101     {
9102         if (name1.empty())
9103             return nullptr;
9104         switch (content_type) {
9105         case CONTENT_BUILDING: {
9106             if (auto bt = GetBuildingType(name1))
9107                 return bt->Location();
9108             break;
9109         }
9110         case CONTENT_SPECIES: {
9111             if (auto s = GetSpecies(name1))
9112                 return s->Location();
9113             break;
9114         }
9115         case CONTENT_SHIP_HULL: {
9116             if (auto h = GetShipHull(name1))
9117                 return h->Location();
9118             break;
9119         }
9120         case CONTENT_SHIP_PART: {
9121             if (auto p = GetShipPart(name1))
9122                 return p->Location();
9123             break;
9124         }
9125         case CONTENT_SPECIAL: {
9126             if (auto s = GetSpecial(name1))
9127                 return s->Location();
9128             break;
9129         }
9130         case CONTENT_FOCUS: {
9131             if (name2.empty())
9132                 return nullptr;
9133             // get species, then focus from that species
9134             if (auto s = GetSpecies(name1)) {
9135                 for (auto& f : s->Foci()) {
9136                     if (f.Name() == name2)
9137                         return f.Location();
9138                 }
9139             }
9140             break;
9141         }
9142         default:
9143             return nullptr;
9144         }
9145         return nullptr;
9146     }
9147 
GetContentTypeName(ContentType content_type)9148     const std::string& GetContentTypeName(ContentType content_type) {
9149         switch (content_type) {
9150         case CONTENT_BUILDING:  return UserString("UIT_BUILDING");          break;
9151         case CONTENT_SPECIES:   return UserString("ENC_SPECIES");           break;
9152         case CONTENT_SHIP_HULL: return UserString("UIT_SHIP_HULL");         break;
9153         case CONTENT_SHIP_PART: return UserString("UIT_SHIP_PART");         break;
9154         case CONTENT_SPECIAL:   return UserString("ENC_SPECIAL");           break;
9155         case CONTENT_FOCUS:     return UserString("PLANETARY_FOCUS_TITLE"); break;
9156         default:                return EMPTY_STRING;                        break;
9157         }
9158     }
9159 }
9160 
Location(ContentType content_type,std::unique_ptr<ValueRef::ValueRef<std::string>> && name1,std::unique_ptr<ValueRef::ValueRef<std::string>> && name2)9161 Location::Location(ContentType content_type,
9162                    std::unique_ptr<ValueRef::ValueRef<std::string>>&& name1,
9163                    std::unique_ptr<ValueRef::ValueRef<std::string>>&& name2) :
9164     Condition(),
9165     m_name1(std::move(name1)),
9166     m_name2(std::move(name2)),
9167     m_content_type(content_type)
9168 {
9169     auto operands = {m_name1.get(), m_name2.get()};
9170     m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
9171     m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); });
9172     m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); });
9173 }
9174 
operator ==(const Condition & rhs) const9175 bool Location::operator==(const Condition& rhs) const {
9176     if (this == &rhs)
9177         return true;
9178     if (typeid(*this) != typeid(rhs))
9179         return false;
9180 
9181     const Location& rhs_ = static_cast<const Location&>(rhs);
9182 
9183     if (m_content_type != rhs_.m_content_type)
9184         return false;
9185 
9186     CHECK_COND_VREF_MEMBER(m_name1)
9187     CHECK_COND_VREF_MEMBER(m_name2)
9188 
9189     return true;
9190 }
9191 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const9192 void Location::Eval(const ScriptingContext& parent_context,
9193                     ObjectSet& matches, ObjectSet& non_matches,
9194                     SearchDomain search_domain/* = NON_MATCHES*/) const
9195 {
9196     bool simple_eval_safe = ((!m_name1 || m_name1->LocalCandidateInvariant()) &&
9197                              (!m_name2 || m_name2->LocalCandidateInvariant()) &&
9198                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
9199 
9200     if (simple_eval_safe) {
9201         // evaluate value and range limits once, use to match all candidates
9202 
9203         std::string name1 = (m_name1 ? m_name1->Eval(parent_context) : "");
9204         std::string name2 = (m_name2 ? m_name2->Eval(parent_context) : "");
9205 
9206         // get condition from content, apply to matches / non_matches
9207         const auto condition = GetLocationCondition(m_content_type, name1, name2);
9208         if (condition && condition != this) {
9209             condition->Eval(parent_context, matches, non_matches, search_domain);
9210         } else {
9211             // if somehow in a cyclical loop because some content's location
9212             // was defined as Location or if there is no location
9213             // condition, match nothing
9214             if (search_domain == MATCHES) {
9215                 non_matches.insert(non_matches.end(), matches.begin(), matches.end());
9216                 matches.clear();
9217             }
9218         }
9219 
9220     } else {
9221         // re-evaluate value and ranges for each candidate object
9222         Condition::Eval(parent_context, matches, non_matches, search_domain);
9223     }
9224 }
9225 
Description(bool negated) const9226 std::string Location::Description(bool negated/* = false*/) const {
9227     std::string name1_str;
9228     if (m_name1)
9229         name1_str = m_name1->Description();
9230 
9231     std::string name2_str;
9232     if (m_name2)
9233         name2_str = m_name2->Description();
9234 
9235     std::string content_type_str = GetContentTypeName(m_content_type);
9236     std::string name_str = (m_content_type == CONTENT_FOCUS ? name2_str : name1_str);
9237 
9238     return str(FlexibleFormat((!negated)
9239                ? UserString("DESC_LOCATION")
9240                : UserString("DESC_LOCATION_NOT"))
9241                % content_type_str
9242                % name_str);
9243 }
9244 
Dump(unsigned short ntabs) const9245 std::string Location::Dump(unsigned short ntabs) const {
9246     std::string retval = DumpIndent(ntabs) + "Location content_type = ";
9247 
9248     switch (m_content_type) {
9249     case CONTENT_BUILDING:  retval += "Building";   break;
9250     case CONTENT_FOCUS:     retval += "Focus";      break;
9251     case CONTENT_SHIP_HULL: retval += "Hull";       break;
9252     case CONTENT_SHIP_PART: retval += "Part";       break;
9253     case CONTENT_SPECIAL:   retval += "Special";    break;
9254     case CONTENT_SPECIES:   retval += "Species";    break;
9255     default:                retval += "???";
9256     }
9257 
9258     if (m_name1)
9259         retval += " name1 = " + m_name1->Dump(ntabs);
9260     if (m_name2)
9261         retval += " name2 = " + m_name2->Dump(ntabs);
9262     return retval;
9263 }
9264 
Match(const ScriptingContext & local_context) const9265 bool Location::Match(const ScriptingContext& local_context) const {
9266     auto candidate = local_context.condition_local_candidate;
9267     if (!candidate) {
9268         ErrorLogger() << "Location::Match passed no candidate object";
9269         return false;
9270     }
9271 
9272     std::string name1 = (m_name1 ? m_name1->Eval(local_context) : "");
9273     std::string name2 = (m_name2 ? m_name2->Eval(local_context) : "");
9274 
9275     const auto condition = GetLocationCondition(m_content_type, name1, name2);
9276     if (!condition || condition == this)
9277         return false;
9278 
9279     // other Conditions' Match functions not directly callable, so can't do any
9280     // better than just calling Eval for each candidate...
9281     return condition->Eval(local_context, candidate);
9282 }
9283 
SetTopLevelContent(const std::string & content_name)9284 void Location::SetTopLevelContent(const std::string& content_name) {
9285     if (m_name1)
9286         m_name1->SetTopLevelContent(content_name);
9287     if (m_name2)
9288         m_name2->SetTopLevelContent(content_name);
9289 }
9290 
GetCheckSum() const9291 unsigned int Location::GetCheckSum() const {
9292     unsigned int retval{0};
9293 
9294     CheckSums::CheckSumCombine(retval, "Condition::Location");
9295     CheckSums::CheckSumCombine(retval, m_name1);
9296     CheckSums::CheckSumCombine(retval, m_name2);
9297     CheckSums::CheckSumCombine(retval, m_content_type);
9298 
9299     TraceLogger() << "GetCheckSum(Location): retval: " << retval;
9300     return retval;
9301 }
9302 
9303 ///////////////////////////////////////////////////////////
9304 // CombatTarget                                          //
9305 ///////////////////////////////////////////////////////////
9306 namespace {
GetCombatTargetCondition(ContentType content_type,const std::string & name)9307     const Condition* GetCombatTargetCondition(
9308         ContentType content_type, const std::string& name)
9309     {
9310         if (name.empty())
9311             return nullptr;
9312         switch (content_type) {
9313         case CONTENT_SPECIES: {
9314             if (auto s = GetSpecies(name))
9315                 return s->CombatTargets();
9316             break;
9317         }
9318         case CONTENT_SHIP_PART: {
9319             if (auto p = GetShipPart(name))
9320                 return p->CombatTargets();
9321             break;
9322         }
9323         case CONTENT_BUILDING:
9324         case CONTENT_SHIP_HULL:
9325         case CONTENT_SPECIAL:
9326         case CONTENT_FOCUS:
9327         default:
9328             return nullptr;
9329         }
9330         return nullptr;
9331     }
9332 }
9333 
CombatTarget(ContentType content_type,std::unique_ptr<ValueRef::ValueRef<std::string>> && name)9334 CombatTarget::CombatTarget(ContentType content_type,
9335                            std::unique_ptr<ValueRef::ValueRef<std::string>>&& name) :
9336     Condition(),
9337     m_name(std::move(name)),
9338     m_content_type(content_type)
9339 {
9340     m_root_candidate_invariant = !m_name || m_name->RootCandidateInvariant();
9341     m_target_invariant = !m_name|| m_name->TargetInvariant();
9342     m_source_invariant = !m_name || m_name->SourceInvariant();
9343 }
9344 
operator ==(const Condition & rhs) const9345 bool CombatTarget::operator==(const Condition& rhs) const {
9346     if (this == &rhs)
9347         return true;
9348     if (typeid(*this) != typeid(rhs))
9349         return false;
9350 
9351     const CombatTarget& rhs_ = static_cast<const CombatTarget&>(rhs);
9352 
9353     if (m_content_type != rhs_.m_content_type)
9354         return false;
9355 
9356     CHECK_COND_VREF_MEMBER(m_name)
9357 
9358     return true;
9359 }
9360 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const9361 void CombatTarget::Eval(const ScriptingContext& parent_context,
9362                         ObjectSet& matches, ObjectSet& non_matches,
9363                         SearchDomain search_domain/* = NON_MATCHES*/) const
9364 {
9365     bool simple_eval_safe = ((!m_name || m_name->LocalCandidateInvariant()) &&
9366                              (parent_context.condition_root_candidate || RootCandidateInvariant()));
9367 
9368     if (simple_eval_safe) {
9369         // evaluate value and range limits once, use to match all candidates
9370 
9371         std::string name = (m_name ? m_name->Eval(parent_context) : "");
9372 
9373         // get condition from content, apply to matches / non_matches
9374         const auto condition = GetCombatTargetCondition(m_content_type, name);
9375         if (condition && condition != this) {
9376             condition->Eval(parent_context, matches, non_matches, search_domain);
9377         } else {
9378             // if somehow in a cyclical loop because some content's location
9379             // was defined as CombatTarget or if there is no available combat
9380             // targetting condition (eg. in valid content type, or name of
9381             // a bit of content that doesn't exist), match nothing
9382             if (search_domain == MATCHES) {
9383                 non_matches.insert(non_matches.end(), matches.begin(), matches.end());
9384                 matches.clear();
9385             }
9386         }
9387 
9388     } else {
9389         // re-evaluate value and ranges for each candidate object
9390         Condition::Eval(parent_context, matches, non_matches, search_domain);
9391     }
9392 }
9393 
Description(bool negated) const9394 std::string CombatTarget::Description(bool negated/* = false*/) const {
9395     std::string name_str;
9396     if (m_name)
9397         name_str = m_name->Description();
9398 
9399     std::string content_type_str = GetContentTypeName(m_content_type);
9400 
9401     return str(FlexibleFormat((!negated)
9402                ? UserString("DESC_COMBAT_TARGET")
9403                : UserString("DESC_COMBAT_TARGET_NOT"))
9404                % content_type_str
9405                % name_str);
9406 }
9407 
Dump(unsigned short ntabs) const9408 std::string CombatTarget::Dump(unsigned short ntabs) const {
9409     std::string retval = DumpIndent(ntabs) + "CombatTarget content_type = ";
9410 
9411     switch (m_content_type) {
9412     case CONTENT_BUILDING:  retval += "Building";   break;
9413     case CONTENT_FOCUS:     retval += "Focus";      break;
9414     case CONTENT_SHIP_HULL: retval += "Hull";       break;
9415     case CONTENT_SHIP_PART: retval += "Part";       break;
9416     case CONTENT_SPECIAL:   retval += "Special";    break;
9417     case CONTENT_SPECIES:   retval += "Species";    break;
9418     default:                retval += "???";
9419     }
9420 
9421     if (m_name)
9422         retval += " name = " + m_name->Dump(ntabs);
9423     return retval;
9424 }
9425 
Match(const ScriptingContext & local_context) const9426 bool CombatTarget::Match(const ScriptingContext& local_context) const {
9427     auto candidate = local_context.condition_local_candidate;
9428     if (!candidate) {
9429         ErrorLogger() << "CombatTarget::Match passed no candidate object";
9430         return false;
9431     }
9432 
9433     std::string name = (m_name ? m_name->Eval(local_context) : "");
9434 
9435     const auto condition = GetCombatTargetCondition(m_content_type, name);
9436     if (!condition || condition == this)
9437         return false;
9438 
9439     // other Conditions' Match functions not directly callable, so can't do any
9440     // better than just calling Eval for each candidate...
9441     return condition->Eval(local_context, candidate);
9442 }
9443 
SetTopLevelContent(const std::string & content_name)9444 void CombatTarget::SetTopLevelContent(const std::string& content_name) {
9445     if (m_name)
9446         m_name->SetTopLevelContent(content_name);
9447 }
9448 
GetCheckSum() const9449 unsigned int CombatTarget::GetCheckSum() const {
9450     unsigned int retval{0};
9451 
9452     CheckSums::CheckSumCombine(retval, "Condition::CombatTarget");
9453     CheckSums::CheckSumCombine(retval, m_name);
9454     CheckSums::CheckSumCombine(retval, m_content_type);
9455 
9456     TraceLogger() << "GetCheckSum(CombatTarget): retval: " << retval;
9457     return retval;
9458 }
9459 
9460 ///////////////////////////////////////////////////////////
9461 // And                                                   //
9462 ///////////////////////////////////////////////////////////
And(std::vector<std::unique_ptr<Condition>> && operands)9463 And::And(std::vector<std::unique_ptr<Condition>>&& operands) :
9464     Condition(),
9465     m_operands(std::move(operands))
9466 {
9467     m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
9468     m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); });
9469     m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); });
9470 }
9471 
And(std::unique_ptr<Condition> && operand1,std::unique_ptr<Condition> && operand2,std::unique_ptr<Condition> && operand3,std::unique_ptr<Condition> && operand4)9472 And::And(std::unique_ptr<Condition>&& operand1, std::unique_ptr<Condition>&& operand2,
9473          std::unique_ptr<Condition>&& operand3, std::unique_ptr<Condition>&& operand4) :
9474     Condition()
9475 {
9476     // would prefer to initialize the vector m_operands in the initializer list, but this is difficult with non-copyable unique_ptr parameters
9477     if (operand1)
9478         m_operands.push_back(std::move(operand1));
9479     if (operand2)
9480         m_operands.push_back(std::move(operand2));
9481     if (operand3)
9482         m_operands.push_back(std::move(operand3));
9483     if (operand4)
9484         m_operands.push_back(std::move(operand4));
9485 
9486     m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
9487     m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); });
9488     m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); });
9489 }
9490 
operator ==(const Condition & rhs) const9491 bool And::operator==(const Condition& rhs) const {
9492     if (this == &rhs)
9493         return true;
9494     if (typeid(*this) != typeid(rhs))
9495         return false;
9496 
9497     const And& rhs_ = static_cast<const And&>(rhs);
9498 
9499     if (m_operands.size() != rhs_.m_operands.size())
9500         return false;
9501     for (unsigned int i = 0; i < m_operands.size(); ++i) {
9502         CHECK_COND_VREF_MEMBER(m_operands.at(i))
9503     }
9504 
9505     return true;
9506 }
9507 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const9508 void And::Eval(const ScriptingContext& parent_context, ObjectSet& matches,
9509                ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const
9510 {
9511     if (m_operands.empty()) {
9512         ErrorLogger() << "And::Eval given no operands!";
9513         return;
9514     }
9515     for (auto& operand : m_operands) {
9516         if (!operand) {
9517             ErrorLogger() << "And::Eval given null operand!";
9518             return;
9519         }
9520     }
9521 
9522     auto ObjList = [](const ObjectSet& objs) -> std::string {
9523         std::stringstream ss;
9524         for (const auto& obj : objs)
9525             ss << obj->Name() << " (" << std::to_string(obj->ID()) << ")  ";
9526         return ss.str();
9527     };
9528 
9529     TraceLogger(conditions) << "And::Eval searching " << (search_domain == MATCHES ? "matches" : "non_matches")
9530                             << " with input matches (" << matches.size() << "): " << ObjList(matches)
9531                             << " and input non_matches(" << non_matches.size() << "): " << ObjList(non_matches);
9532 
9533     if (search_domain == NON_MATCHES) {
9534         ObjectSet partly_checked_non_matches;
9535         partly_checked_non_matches.reserve(non_matches.size());
9536 
9537         // move items in non_matches set that pass first operand condition into
9538         // partly_checked_non_matches set
9539         m_operands[0]->Eval(parent_context, partly_checked_non_matches, non_matches, NON_MATCHES);
9540         TraceLogger(conditions) << "Subcondition: " << m_operands[0]->Dump()
9541                                 <<"\npartly_checked_non_matches (" << partly_checked_non_matches.size() << "): " << ObjList(partly_checked_non_matches);
9542 
9543         // move items that don't pass one of the other conditions back to non_matches
9544         for (unsigned int i = 1; i < m_operands.size(); ++i) {
9545             if (partly_checked_non_matches.empty()) break;
9546             m_operands[i]->Eval(parent_context, partly_checked_non_matches, non_matches, MATCHES);
9547             TraceLogger(conditions) << "Subcondition: " << m_operands[i]->Dump()
9548                                     <<"\npartly_checked_non_matches (" << partly_checked_non_matches.size() << "): " << ObjList(partly_checked_non_matches);
9549         }
9550 
9551         // merge items that passed all operand conditions into matches
9552         matches.insert(matches.end(), partly_checked_non_matches.begin(), partly_checked_non_matches.end());
9553 
9554         // items already in matches set are not checked, and remain in matches set even if
9555         // they don't match one of the operand conditions
9556 
9557     } else /*(search_domain == MATCHES)*/ {
9558         // check all operand conditions on all objects in the matches set, moving those
9559         // that don't pass a condition to the non-matches set
9560 
9561         for (auto& operand : m_operands) {
9562             if (matches.empty()) break;
9563             operand->Eval(parent_context, matches, non_matches, MATCHES);
9564             TraceLogger(conditions) << "Subcondition: " << operand->Dump()
9565                                     <<"\nremaining matches (" << matches.size() << "): " << ObjList(matches);
9566         }
9567 
9568         // items already in non_matches set are not checked, and remain in non_matches set
9569         // even if they pass all operand conditions
9570     }
9571     TraceLogger(conditions) << "And::Eval final matches (" << matches.size() << "): " << ObjList(matches)
9572                             << " and non_matches (" << non_matches.size() << "): " << ObjList(non_matches);
9573 }
9574 
Description(bool negated) const9575 std::string And::Description(bool negated/* = false*/) const {
9576     std::string values_str;
9577     if (m_operands.size() == 1) {
9578         values_str += (!negated)
9579             ? UserString("DESC_AND_BEFORE_SINGLE_OPERAND")
9580             : UserString("DESC_NOT_AND_BEFORE_SINGLE_OPERAND");
9581         // Pushing the negation to the enclosed conditions
9582         values_str += m_operands[0]->Description(negated);
9583         values_str += (!negated)
9584             ? UserString("DESC_AND_AFTER_SINGLE_OPERAND")
9585             : UserString("DESC_NOT_AND_AFTER_SINGLE_OPERAND");
9586     } else {
9587         values_str += (!negated)
9588             ? UserString("DESC_AND_BEFORE_OPERANDS")
9589             : UserString("DESC_NOT_AND_BEFORE_OPERANDS");
9590         for (unsigned int i = 0; i < m_operands.size(); ++i) {
9591             // Pushing the negation to the enclosed conditions
9592             values_str += m_operands[i]->Description(negated);
9593             if (i != m_operands.size() - 1) {
9594                 values_str += (!negated)
9595                     ? UserString("DESC_AND_BETWEEN_OPERANDS")
9596                     : UserString("DESC_NOT_AND_BETWEEN_OPERANDS");
9597             }
9598         }
9599         values_str += (!negated)
9600             ? UserString("DESC_AND_AFTER_OPERANDS")
9601             : UserString("DESC_NOT_AND_AFTER_OPERANDS");
9602     }
9603     return values_str;
9604 }
9605 
Dump(unsigned short ntabs) const9606 std::string And::Dump(unsigned short ntabs) const {
9607     std::string retval = DumpIndent(ntabs) + "And [\n";
9608     for (auto& operand : m_operands)
9609         retval += operand->Dump(ntabs+1);
9610     retval += DumpIndent(ntabs) + "]\n";
9611     return retval;
9612 }
9613 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const9614 void And::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const {
9615     if (!m_operands.empty()) {
9616         m_operands[0]->GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); // gets condition_non_targets from first operand condition
9617     } else {
9618         Condition::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
9619     }
9620 }
9621 
SetTopLevelContent(const std::string & content_name)9622 void And::SetTopLevelContent(const std::string& content_name) {
9623     for (auto& operand : m_operands) {
9624         operand->SetTopLevelContent(content_name);
9625     }
9626 }
9627 
GetCheckSum() const9628 unsigned int And::GetCheckSum() const {
9629     unsigned int retval{0};
9630 
9631     CheckSums::CheckSumCombine(retval, "Condition::And");
9632     CheckSums::CheckSumCombine(retval, m_operands);
9633 
9634     TraceLogger() << "GetCheckSum(And): retval: " << retval;
9635     return retval;
9636 }
9637 
Operands() const9638 const std::vector<Condition*> And::Operands() const {
9639     std::vector<Condition*> retval(m_operands.size());
9640     std::transform(m_operands.begin(), m_operands.end(), retval.begin(),
9641                    [](const std::unique_ptr<Condition>& xx) {return xx.get();});
9642     return retval;
9643 }
9644 
9645 ///////////////////////////////////////////////////////////
9646 // Or                                                    //
9647 ///////////////////////////////////////////////////////////
Or(std::vector<std::unique_ptr<Condition>> && operands)9648 Or::Or(std::vector<std::unique_ptr<Condition>>&& operands) :
9649     Condition(),
9650     m_operands(std::move(operands))
9651 {
9652     m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
9653     m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); });
9654     m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); });
9655 }
9656 
Or(std::unique_ptr<Condition> && operand1,std::unique_ptr<Condition> && operand2,std::unique_ptr<Condition> && operand3,std::unique_ptr<Condition> && operand4)9657 Or::Or(std::unique_ptr<Condition>&& operand1,
9658        std::unique_ptr<Condition>&& operand2,
9659        std::unique_ptr<Condition>&& operand3,
9660        std::unique_ptr<Condition>&& operand4) :
9661     Condition()
9662 {
9663     // would prefer to initialize the vector m_operands in the initializer list, but this is difficult with non-copyable unique_ptr parameters
9664     if (operand1)
9665         m_operands.push_back(std::move(operand1));
9666     if (operand2)
9667         m_operands.push_back(std::move(operand2));
9668     if (operand3)
9669         m_operands.push_back(std::move(operand3));
9670     if (operand4)
9671         m_operands.push_back(std::move(operand4));
9672 
9673     m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
9674     m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); });
9675     m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); });
9676 }
9677 
operator ==(const Condition & rhs) const9678 bool Or::operator==(const Condition& rhs) const {
9679     if (this == &rhs)
9680         return true;
9681     if (typeid(*this) != typeid(rhs))
9682         return false;
9683 
9684     const Or& rhs_ = static_cast<const Or&>(rhs);
9685 
9686     if (m_operands.size() != rhs_.m_operands.size())
9687         return false;
9688     for (unsigned int i = 0; i < m_operands.size(); ++i) {
9689         CHECK_COND_VREF_MEMBER(m_operands.at(i))
9690     }
9691 
9692     return true;
9693 }
9694 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const9695 void Or::Eval(const ScriptingContext& parent_context, ObjectSet& matches,
9696               ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const
9697 {
9698     if (m_operands.empty()) {
9699         ErrorLogger() << "Or::Eval given no operands!";
9700         return;
9701     }
9702     for (auto& operand : m_operands) {
9703         if (!operand) {
9704             ErrorLogger() << "Or::Eval given null operand!";
9705             return;
9706         }
9707     }
9708 
9709     if (search_domain == NON_MATCHES) {
9710         // check each item in the non-matches set against each of the operand conditions
9711         // if a non-candidate item matches an operand condition, move the item to the
9712         // matches set.
9713 
9714         for (auto& operand : m_operands) {
9715             if (non_matches.empty()) break;
9716             operand->Eval(parent_context, matches, non_matches, NON_MATCHES);
9717         }
9718 
9719         // items already in matches set are not checked and remain in the
9720         // matches set even if they fail all the operand conditions
9721 
9722     } else {
9723         ObjectSet partly_checked_matches;
9724         partly_checked_matches.reserve(matches.size());
9725 
9726         // move items in matches set the fail the first operand condition into
9727         // partly_checked_matches set
9728         m_operands[0]->Eval(parent_context, matches, partly_checked_matches, MATCHES);
9729 
9730         // move items that pass any of the other conditions back into matches
9731         for (auto& operand : m_operands) {
9732             if (partly_checked_matches.empty()) break;
9733             operand->Eval(parent_context, matches, partly_checked_matches, NON_MATCHES);
9734         }
9735 
9736         // merge items that failed all operand conditions into non_matches
9737         non_matches.insert(non_matches.end(), partly_checked_matches.begin(), partly_checked_matches.end());
9738 
9739         // items already in non_matches set are not checked and remain in
9740         // non_matches set even if they pass one or more of the operand
9741         // conditions
9742     }
9743 }
9744 
Description(bool negated) const9745 std::string Or::Description(bool negated/* = false*/) const {
9746     std::string values_str;
9747     if (m_operands.size() == 1) {
9748         values_str += (!negated)
9749             ? UserString("DESC_OR_BEFORE_SINGLE_OPERAND")
9750             : UserString("DESC_NOT_OR_BEFORE_SINGLE_OPERAND");
9751         // Pushing the negation to the enclosed conditions
9752         values_str += m_operands[0]->Description(negated);
9753         values_str += (!negated)
9754             ? UserString("DESC_OR_AFTER_SINGLE_OPERAND")
9755             : UserString("DESC_NOT_OR_AFTER_SINGLE_OPERAND");
9756     } else {
9757         // TODO: use per-operand-type connecting language
9758         values_str += (!negated)
9759             ? UserString("DESC_OR_BEFORE_OPERANDS")
9760             : UserString("DESC_NOT_OR_BEFORE_OPERANDS");
9761         for (unsigned int i = 0; i < m_operands.size(); ++i) {
9762             // Pushing the negation to the enclosed conditions
9763             values_str += m_operands[i]->Description(negated);
9764             if (i != m_operands.size() - 1) {
9765                 values_str += (!negated)
9766                     ? UserString("DESC_OR_BETWEEN_OPERANDS")
9767                     : UserString("DESC_NOT_OR_BETWEEN_OPERANDS");
9768             }
9769         }
9770         values_str += (!negated)
9771             ? UserString("DESC_OR_AFTER_OPERANDS")
9772             : UserString("DESC_NOT_OR_AFTER_OPERANDS");
9773     }
9774     return values_str;
9775 }
9776 
GetDefaultInitialCandidateObjects(const ScriptingContext & parent_context,ObjectSet & condition_non_targets) const9777 void Or::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const {
9778     if (m_operands.empty())
9779         return;
9780 
9781     if (m_operands.size() == 1) {
9782         // get condition_non_targets from the single / only operand condition
9783         m_operands[0]->GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
9784         return;
9785     }
9786 
9787     if (parent_context.source && m_operands.size() == 2) {
9788         if (/*auto* src_condition =*/ dynamic_cast<const Source*>(m_operands[0].get())) {
9789             // special case when first of two subconditions is just Source:
9790             // get the default candidates of the second and add the source if
9791             // it is not already present.
9792             // TODO: extend to other single-match conditions: RootCandidate, Target
9793             // TODO: predetermine this situation to avoid repeat runtime dynamic-casts
9794             // TODO: fancier deep inspection of m_operands to determine optimal
9795             //       way to combine the default candidates...
9796 
9797             m_operands[1]->GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
9798             if (std::find(condition_non_targets.begin(), condition_non_targets.end(), parent_context.source) ==
9799                 condition_non_targets.end())
9800             { condition_non_targets.push_back(parent_context.source); }
9801             return;
9802         }
9803     }
9804 
9805     // default / fallback
9806     Condition::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
9807 
9808     // Also tried looping over all subconditions in m_operands and putting all
9809     // of their initial candidates into an unordered_set (to remove duplicates)
9810     // and then copying the result back into condition_non_targets but this was
9811     // substantially slower for many Or conditions
9812 }
9813 
Dump(unsigned short ntabs) const9814 std::string Or::Dump(unsigned short ntabs) const {
9815     std::string retval = DumpIndent(ntabs) + "Or [\n";
9816     for (auto& operand : m_operands)
9817         retval += operand->Dump(ntabs+1);
9818     retval += "\n" + DumpIndent(ntabs) + "]\n";
9819     return retval;
9820 }
9821 
SetTopLevelContent(const std::string & content_name)9822 void Or::SetTopLevelContent(const std::string& content_name) {
9823     for (auto& operand : m_operands) {
9824         operand->SetTopLevelContent(content_name);
9825     }
9826 }
9827 
GetCheckSum() const9828 unsigned int Or::GetCheckSum() const {
9829     unsigned int retval{0};
9830 
9831     CheckSums::CheckSumCombine(retval, "Condition::Or");
9832     CheckSums::CheckSumCombine(retval, m_operands);
9833 
9834     TraceLogger() << "GetCheckSum(Or): retval: " << retval;
9835     return retval;
9836 }
9837 
9838 ///////////////////////////////////////////////////////////
9839 // Not                                                   //
9840 ///////////////////////////////////////////////////////////
Not(std::unique_ptr<Condition> && operand)9841 Not::Not(std::unique_ptr<Condition>&& operand) :
9842     Condition(),
9843     m_operand(std::move(operand))
9844 {
9845     m_root_candidate_invariant = m_operand->RootCandidateInvariant();
9846     m_target_invariant = m_operand->TargetInvariant();
9847     m_source_invariant = m_operand->SourceInvariant();
9848 }
9849 
operator ==(const Condition & rhs) const9850 bool Not::operator==(const Condition& rhs) const {
9851     if (this == &rhs)
9852         return true;
9853     if (typeid(*this) != typeid(rhs))
9854         return false;
9855 
9856     const Not& rhs_ = static_cast<const Not&>(rhs);
9857 
9858     CHECK_COND_VREF_MEMBER(m_operand)
9859 
9860     return true;
9861 }
9862 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const9863 void Not::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches,
9864                SearchDomain search_domain/* = NON_MATCHES*/) const
9865 {
9866     if (!m_operand) {
9867         ErrorLogger() << "Not::Eval found no subcondition to evaluate!";
9868         return;
9869     }
9870 
9871     if (search_domain == NON_MATCHES) {
9872         // search non_matches set for items that don't meet the operand
9873         // condition, and move those to the matches set
9874         m_operand->Eval(parent_context, non_matches, matches, MATCHES); // swapping order of matches and non_matches set parameters and MATCHES / NON_MATCHES search domain effects NOT on requested search domain
9875     } else {
9876         // search matches set for items that meet the operand condition
9877         // condition, and move those to the non_matches set
9878         m_operand->Eval(parent_context, non_matches, matches, NON_MATCHES);
9879     }
9880 }
9881 
Description(bool negated) const9882 std::string Not::Description(bool negated/* = false*/) const
9883 { return m_operand->Description(!negated); }
9884 
Dump(unsigned short ntabs) const9885 std::string Not::Dump(unsigned short ntabs) const {
9886     std::string retval = DumpIndent(ntabs) + "Not\n";
9887     retval += m_operand->Dump(ntabs+1);
9888     return retval;
9889 }
9890 
SetTopLevelContent(const std::string & content_name)9891 void Not::SetTopLevelContent(const std::string& content_name) {
9892     if (m_operand)
9893         m_operand->SetTopLevelContent(content_name);
9894 }
9895 
GetCheckSum() const9896 unsigned int Not::GetCheckSum() const {
9897     unsigned int retval{0};
9898 
9899     CheckSums::CheckSumCombine(retval, "Condition::Not");
9900     CheckSums::CheckSumCombine(retval, m_operand);
9901 
9902     TraceLogger() << "GetCheckSum(Not): retval: " << retval;
9903     return retval;
9904 }
9905 
9906 ///////////////////////////////////////////////////////////
9907 // OrderedAlternativesOf
9908 ///////////////////////////////////////////////////////////
FCMoveContent(ObjectSet & from_set,ObjectSet & to_set)9909 void FCMoveContent(ObjectSet& from_set, ObjectSet& to_set) {
9910     to_set.insert(to_set.end(),
9911                   std::make_move_iterator(from_set.begin()),
9912                   std::make_move_iterator(from_set.end()));
9913     from_set.clear();
9914 }
9915 
OrderedAlternativesOf(std::vector<std::unique_ptr<Condition>> && operands)9916 OrderedAlternativesOf::OrderedAlternativesOf(
9917     std::vector<std::unique_ptr<Condition>>&& operands) :
9918     Condition(),
9919     m_operands(std::move(operands))
9920 {
9921     m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); });
9922     m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); });
9923     m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); });
9924 }
9925 
operator ==(const Condition & rhs) const9926 bool OrderedAlternativesOf::operator==(const Condition& rhs) const {
9927     if (this == &rhs)
9928         return true;
9929     if (typeid(*this) != typeid(rhs))
9930         return false;
9931 
9932     const OrderedAlternativesOf& rhs_ = static_cast<const OrderedAlternativesOf&>(rhs);
9933 
9934     if (m_operands.size() != rhs_.m_operands.size())
9935         return false;
9936     for (unsigned int i = 0; i < m_operands.size(); ++i) {
9937         CHECK_COND_VREF_MEMBER(m_operands.at(i))
9938     }
9939 
9940     return true;
9941 }
9942 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const9943 void OrderedAlternativesOf::Eval(const ScriptingContext& parent_context,
9944                                  ObjectSet& matches, ObjectSet& non_matches,
9945                                  SearchDomain search_domain/* = NON_MATCHES*/) const
9946 {
9947     if (m_operands.empty()) {
9948         ErrorLogger() << "OrderedAlternativesOf::Eval given no operands!";
9949         return;
9950     }
9951     for (auto& operand : m_operands) {
9952         if (!operand) {
9953             ErrorLogger() << "OrderedAlternativesOf::Eval given null operand!";
9954             return;
9955         }
9956     }
9957 
9958     // OrderedAlternativesOf [ A B C ] matches all candidates which match the topmost condition.
9959     // If any candidate matches A, then all candidates that match A are matched,
9960     // or if no candidate matches A but any candidate matches B, then all candidates that match B are matched,
9961     // or if no candidate matches A or B but any candidate matches C, then all candidates that match C are matched.
9962     // If no candidate matches A, B, or C, then nothing is matched.
9963     //
9964     // Not OrderedAlternativesOf [ A B C ] finds the topmost condition which has matches and then matches its non-matches.
9965     // If any candidate matches A, then all candidates that do not match A are matched,
9966     // or if no candidates match A but any candidate matches B, then all candidates that do not match B are matched,
9967     // or if no candidate matches A or B but any candidate matches C, then all candidates that do not match C are matched.
9968     // If no candidate matches A, B, or C, then all candidates are matched.
9969     if (search_domain == NON_MATCHES) {
9970         // Check each operand condition on objects in the input matches and non_matches sets, until an operand condition matches an object.
9971         // If an operand condition is selected, apply it to the input non_matches set, moving matching candidates to matches.
9972         // If no operand condition is selected, because no candidate is matched by any operand condition, then do nothing.
9973         ObjectSet temp_objects;
9974         temp_objects.reserve(std::max(matches.size(),non_matches.size()));
9975 
9976         for (auto& operand : m_operands) {
9977             operand->Eval(parent_context, temp_objects, non_matches, NON_MATCHES);
9978             if (!temp_objects.empty()) {
9979                 // Select the current operand condition. Apply it to the NON_MATCHES candidate set.
9980                 // We alread did the application, so we use the results
9981                 matches.reserve(temp_objects.size() + matches.size());
9982                 FCMoveContent(temp_objects, matches);
9983                 return;
9984             }
9985             // Check if the operand condition matches an object in the other input set
9986             operand->Eval(parent_context, matches, temp_objects, MATCHES);
9987             if (!matches.empty()) {
9988                 // Select the current operand condition. Apply it to the NON_MATCHES candidate set.
9989                 // We already did the application before, but there were no matches.
9990                 // restore state before checking the operand
9991                 FCMoveContent(temp_objects, matches);
9992                 return;
9993             }
9994 
9995             // restore state before checking the operand
9996             FCMoveContent(temp_objects, matches);
9997             // try the next operand
9998         }
9999 
10000         // No operand condition was selected. State is restored. Nothing should be moved to matches input set
10001     } else /*(search_domain == MATCHES)*/ {
10002         // Check each operand condition on objects in the input matches and non_matches sets, until an operand condition matches an object.
10003         // If an operand condition is selected, apply it to the input matches set, moving non-matching candidates to non_matches.
10004         // If no operand condition is selected, because no candidate is matched by any operand condition, then move all of the input matches into non_matches.
10005         ObjectSet temp_objects;
10006         temp_objects.reserve(std::max(matches.size(),non_matches.size()));
10007 
10008         for (auto& operand : m_operands) {
10009             // Apply the current operand optimistically. Select it if there are any matching objects in the input sets
10010             operand->Eval(parent_context, temp_objects, matches, NON_MATCHES);
10011             // temp_objects are objects from input matches set which also match the operand
10012             // matches are objects from input matches set which do not match the operand
10013             if (!temp_objects.empty()) {
10014                 // Select and apply this operand. Objects in matches do not match this condition.
10015                 non_matches.reserve(matches.size() + non_matches.size());
10016                 FCMoveContent(matches, non_matches);
10017                 FCMoveContent(temp_objects, matches);
10018                 return;
10019             }
10020             // Select this operand if there are matching objects in the non_matches input set.
10021             operand->Eval(parent_context, temp_objects, non_matches, NON_MATCHES);
10022             if (!temp_objects.empty()) {
10023                 // Select and apply this operand. But no matching objects exist in the matches input set,
10024                 // so all objects need to be moved into the non_matches set
10025                 non_matches.reserve(matches.size() + non_matches.size() + temp_objects.size());
10026                 FCMoveContent(matches, non_matches);
10027                 FCMoveContent(temp_objects, non_matches);
10028                 return;
10029             }
10030 
10031             // Operand was not selected. Restore state before. Try next operand.
10032             FCMoveContent(temp_objects, matches);
10033         }
10034 
10035         // No operand condition was selected. Objects in matches input set do not match, so move those to non_matches input set.
10036         non_matches.reserve(matches.size() + non_matches.size());
10037         FCMoveContent(matches, non_matches);
10038     }
10039 }
10040 
Description(bool negated) const10041 std::string OrderedAlternativesOf::Description(bool negated/* = false*/) const {
10042     std::string values_str;
10043     if (m_operands.size() == 1) {
10044         values_str += (!negated)
10045             ? UserString("DESC_ORDERED_ALTERNATIVES_BEFORE_SINGLE_OPERAND")
10046             : UserString("DESC_NOT_ORDERED_ALTERNATIVES_BEFORE_SINGLE_OPERAND");
10047         // Pushing the negation of matches to the enclosed conditions
10048         values_str += m_operands[0]->Description(negated);
10049         values_str += (!negated)
10050             ? UserString("DESC_ORDERED_ALTERNATIVES_AFTER_SINGLE_OPERAND")
10051             : UserString("DESC_NOT_ORDERED_ALTERNATIVES_AFTER_SINGLE_OPERAND");
10052     } else {
10053         // TODO: use per-operand-type connecting language
10054         values_str += (!negated)
10055             ? UserString("DESC_ORDERED_ALTERNATIVES_BEFORE_OPERANDS")
10056             : UserString("DESC_NOT_ORDERED_ALTERNATIVES_BEFORE_OPERANDS");
10057         for (unsigned int i = 0; i < m_operands.size(); ++i) {
10058             // Pushing the negation of matches to the enclosed conditions
10059             values_str += m_operands[i]->Description(negated);
10060             if (i != m_operands.size() - 1) {
10061                 values_str += (!negated)
10062                     ? UserString("DESC_ORDERED_ALTERNATIVES_BETWEEN_OPERANDS")
10063                     : UserString("DESC_NOT_ORDERED_ALTERNATIVES_BETWEEN_OPERANDS");
10064             }
10065         }
10066         values_str += (!negated)
10067             ? UserString("DESC_ORDERED_ALTERNATIVES_AFTER_OPERANDS")
10068             : UserString("DESC_NOT_ORDERED_ALTERNATIVES_AFTER_OPERANDS");
10069     }
10070     return values_str;
10071 }
10072 
Dump(unsigned short ntabs) const10073 std::string OrderedAlternativesOf::Dump(unsigned short ntabs) const {
10074     std::string retval = DumpIndent(ntabs) + "OrderedAlternativesOf [\n";
10075     for (auto& operand : m_operands)
10076         retval += operand->Dump(ntabs+1);
10077     retval += DumpIndent(ntabs) + "]\n";
10078     return retval;
10079 }
10080 
SetTopLevelContent(const std::string & content_name)10081 void OrderedAlternativesOf::SetTopLevelContent(const std::string& content_name) {
10082     for (auto& operand : m_operands) {
10083         operand->SetTopLevelContent(content_name);
10084     }
10085 }
10086 
GetCheckSum() const10087 unsigned int OrderedAlternativesOf::GetCheckSum() const {
10088     unsigned int retval{0};
10089 
10090     CheckSums::CheckSumCombine(retval, "Condition::OrderedAlternativesOf");
10091     CheckSums::CheckSumCombine(retval, m_operands);
10092 
10093     TraceLogger() << "GetCheckSum(OrderedAlternativesOf): retval: " << retval;
10094     return retval;
10095 }
10096 
Operands() const10097 const std::vector<Condition*> OrderedAlternativesOf::Operands() const {
10098     std::vector<Condition*> retval(m_operands.size());
10099     std::transform(m_operands.begin(), m_operands.end(), retval.begin(),
10100                    [](const std::unique_ptr<Condition>& xx) {return xx.get();});
10101     return retval;
10102 }
10103 
10104 ///////////////////////////////////////////////////////////
10105 // Described                                             //
10106 ///////////////////////////////////////////////////////////
Described(std::unique_ptr<Condition> && condition,const std::string & desc_stringtable_key)10107 Described::Described(std::unique_ptr<Condition>&& condition, const std::string& desc_stringtable_key) :
10108     Condition(),
10109     m_condition(std::move(condition)),
10110     m_desc_stringtable_key(desc_stringtable_key)
10111 {
10112     m_root_candidate_invariant = !m_condition || m_condition->RootCandidateInvariant();
10113     m_target_invariant = !m_condition || m_condition->TargetInvariant();
10114     m_source_invariant = !m_condition || m_condition->SourceInvariant();
10115 }
10116 
operator ==(const Condition & rhs) const10117 bool Described::operator==(const Condition& rhs) const {
10118     if (this == &rhs)
10119         return true;
10120     if (typeid(*this) != typeid(rhs))
10121         return false;
10122 
10123     const Described& rhs_ = static_cast<const Described&>(rhs);
10124 
10125    if (m_desc_stringtable_key != rhs_.m_desc_stringtable_key)
10126         return false;
10127 
10128     CHECK_COND_VREF_MEMBER(m_condition)
10129 
10130     return true;
10131 }
10132 
Eval(const ScriptingContext & parent_context,ObjectSet & matches,ObjectSet & non_matches,SearchDomain search_domain) const10133 void Described::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches,
10134                      SearchDomain search_domain/* = NON_MATCHES*/) const
10135 {
10136     if (!m_condition) {
10137         ErrorLogger() << "Described::Eval found no subcondition to evaluate!";
10138         return;
10139     }
10140     return m_condition->Eval(parent_context, matches, non_matches, search_domain);
10141 }
10142 
Description(bool negated) const10143 std::string Described::Description(bool negated/* = false*/) const {
10144     if (!m_desc_stringtable_key.empty() && UserStringExists(m_desc_stringtable_key))
10145         return UserString(m_desc_stringtable_key);
10146     if (m_condition)
10147         return m_condition->Description(negated);
10148     return "";
10149 }
10150 
SetTopLevelContent(const std::string & content_name)10151 void Described::SetTopLevelContent(const std::string& content_name) {
10152     if (m_condition)
10153         m_condition->SetTopLevelContent(content_name);
10154 }
10155 
GetCheckSum() const10156 unsigned int Described::GetCheckSum() const {
10157     unsigned int retval{0};
10158 
10159     CheckSums::CheckSumCombine(retval, "Condition::Described");
10160     CheckSums::CheckSumCombine(retval, m_condition);
10161     CheckSums::CheckSumCombine(retval, m_desc_stringtable_key);
10162 
10163     TraceLogger() << "GetCheckSum(Described): retval: " << retval;
10164     return retval;
10165 }
10166 } // namespace Condition
10167