1 #include "ValueRefs.h"
2 
3 #include "Building.h"
4 #include "Fleet.h"
5 #include "ShipDesign.h"
6 #include "ShipPart.h"
7 #include "ShipHull.h"
8 #include "Ship.h"
9 #include "Planet.h"
10 #include "Predicates.h"
11 #include "Species.h"
12 #include "System.h"
13 #include "Field.h"
14 #include "Fighter.h"
15 #include "Pathfinder.h"
16 #include "Universe.h"
17 #include "UniverseObject.h"
18 #include "Enums.h"
19 #include "Tech.h"
20 #include "../Empire/EmpireManager.h"
21 #include "../Empire/Empire.h"
22 #include "../Empire/Supply.h"
23 #include "../util/Random.h"
24 #include "../util/Logger.h"
25 #include "../util/MultiplayerCommon.h"
26 #include "../util/GameRules.h"
27 
28 #include <boost/algorithm/string/classification.hpp>
29 #include <boost/algorithm/string/predicate.hpp>
30 #include <boost/algorithm/string/split.hpp>
31 #include <boost/range/adaptor/filtered.hpp>
32 #include <boost/range/adaptor/map.hpp>
33 #include <boost/range/numeric.hpp>
34 
35 #include <functional>
36 #include <iomanip>
37 #include <iterator>
38 
39 
40 std::string DoubleToString(double val, int digits, bool always_show_sign);
41 bool UserStringExists(const std::string& str);
42 
43 FO_COMMON_API extern const int INVALID_DESIGN_ID;
44 
45 namespace {
FollowReference(std::vector<std::string>::const_iterator first,std::vector<std::string>::const_iterator last,ValueRef::ReferenceType ref_type,const ScriptingContext & context)46     std::shared_ptr<const UniverseObject> FollowReference(
47         std::vector<std::string>::const_iterator first,
48         std::vector<std::string>::const_iterator last,
49         ValueRef::ReferenceType ref_type,
50         const ScriptingContext& context)
51     {
52         //DebugLogger() << "FollowReference: source: " << (context.source ? context.source->Name() : "0")
53         //              << " target: " << (context.effect_target ? context.effect_target->Name() : "0")
54         //              << " local c: " << (context.condition_local_candidate ? context.condition_local_candidate->Name() : "0")
55         //              << " root c: " << (context.condition_root_candidate ? context.condition_root_candidate->Name() : "0");
56 
57         std::shared_ptr<const UniverseObject> obj;
58         switch (ref_type) {
59         case ValueRef::NON_OBJECT_REFERENCE:                    return context.condition_local_candidate;   break;
60         case ValueRef::SOURCE_REFERENCE:                        obj = context.source;                       break;
61         case ValueRef::EFFECT_TARGET_REFERENCE:                 obj = context.effect_target;                break;
62         case ValueRef::CONDITION_ROOT_CANDIDATE_REFERENCE:      obj = context.condition_root_candidate;     break;
63         case ValueRef::CONDITION_LOCAL_CANDIDATE_REFERENCE:
64         default:                                                obj = context.condition_local_candidate;    break;
65         }
66 
67         if (!obj) {
68             std::string type_string;
69             switch (ref_type) {
70             case ValueRef::SOURCE_REFERENCE:                        type_string = "Source";         break;
71             case ValueRef::EFFECT_TARGET_REFERENCE:                 type_string = "Target";         break;
72             case ValueRef::CONDITION_ROOT_CANDIDATE_REFERENCE:      type_string = "RootCandidate";  break;
73             case ValueRef::CONDITION_LOCAL_CANDIDATE_REFERENCE:
74             default:                                                type_string = "LocalCandidate"; break;
75             }
76             ErrorLogger() << "FollowReference : top level object (" << type_string << ") not defined in scripting context";
77             return nullptr;
78         }
79 
80         while (first != last) {
81             std::string property_name = *first;
82             if (property_name == "Planet") {
83                 if (auto b = std::dynamic_pointer_cast<const Building>(obj)) {
84                     obj = context.ContextObjects().get<Planet>(b->PlanetID());
85                 } else {
86                     ErrorLogger() << "FollowReference : object not a building, so can't get its planet.";
87                     obj = nullptr;
88                 }
89             } else if (property_name == "System") {
90                 if (obj)
91                     obj = context.ContextObjects().get<System>(obj->SystemID());
92                 if (!obj)
93                     ErrorLogger() << "FollowReference : Unable to get system for object";
94             } else if (property_name == "Fleet") {
95                 if (auto s = std::dynamic_pointer_cast<const Ship>(obj)) {
96                     obj = context.ContextObjects().get<Fleet>(s->FleetID());
97                 } else {
98                     ErrorLogger() << "FollowReference : object not a ship, so can't get its fleet";
99                     obj = nullptr;
100                 }
101             }
102             ++first;
103         }
104         return obj;
105     }
106 
107     // Generates a debug trace that can be included in error logs, augmenting
108     // the ReconstructName() info with additional info identifying the object
109     // references that were successfully followed.
TraceReference(const std::vector<std::string> & property_name,ValueRef::ReferenceType ref_type,const ScriptingContext & context)110     std::string TraceReference(const std::vector<std::string>& property_name,
111                                ValueRef::ReferenceType ref_type,
112                                const ScriptingContext& context)
113     {
114         std::shared_ptr<const UniverseObject> obj, initial_obj;
115         std::string retval = ReconstructName(property_name, ref_type, false) + " : ";
116         switch (ref_type) {
117         case ValueRef::NON_OBJECT_REFERENCE:
118             retval += " | Non Object Reference |";
119             return retval;
120             break;
121         case ValueRef::SOURCE_REFERENCE:
122             retval += " | Source: ";
123             obj = context.source;
124             break;
125         case ValueRef::EFFECT_TARGET_REFERENCE:
126             retval += " | Effect Target: ";
127             obj = context.effect_target;
128             break;
129         case ValueRef::CONDITION_ROOT_CANDIDATE_REFERENCE:
130             retval += " | Root Candidate: ";
131             obj = context.condition_root_candidate;
132             break;
133         case ValueRef::CONDITION_LOCAL_CANDIDATE_REFERENCE:
134         default:
135             retval += " | Local Candidate: ";
136             obj = context.condition_local_candidate;
137             break;
138         }
139         if (obj) {
140             retval += UserString(boost::lexical_cast<std::string>(obj->ObjectType())) + " "
141                     + std::to_string(obj->ID()) + " ( " + obj->Name() + " ) ";
142             initial_obj = obj;
143         }
144         retval += " | ";
145 
146         auto first = property_name.begin();
147         auto last = property_name.end();
148         while (first != last) {
149             std::string property_name_part = *first;
150             retval += " " + property_name_part + " ";
151             if (property_name_part == "Planet") {
152                 if (auto b = std::dynamic_pointer_cast<const Building>(obj)) {
153                     retval += "(" + std::to_string(b->PlanetID()) + "): ";
154                     obj = context.ContextObjects().get<Planet>(b->PlanetID());
155                 } else
156                     obj = nullptr;
157             } else if (property_name_part == "System") {
158                 if (obj) {
159                     retval += "(" + std::to_string(obj->SystemID()) + "): ";
160                     obj = context.ContextObjects().get<System>(obj->SystemID());
161                 }
162             } else if (property_name_part == "Fleet") {
163                 if (auto s = std::dynamic_pointer_cast<const Ship>(obj))  {
164                     retval += "(" + std::to_string(s->FleetID()) + "): ";
165                     obj = context.ContextObjects().get<Fleet>(s->FleetID());
166                 } else
167                     obj = nullptr;
168             }
169 
170             ++first;
171 
172             if (obj && initial_obj != obj) {
173                 retval += "  Referenced Object: " + UserString(boost::lexical_cast<std::string>(obj->ObjectType())) + " "
174                         + std::to_string(obj->ID()) + " ( " + obj->Name() + " )";
175             }
176             retval += " | ";
177         }
178         return retval;
179     }
180 
181     struct ObjectTypeVisitor : UniverseObjectVisitor {
ObjectTypeVisitor__anonbe5bf2420111::ObjectTypeVisitor182         ObjectTypeVisitor() : m_type(INVALID_UNIVERSE_OBJECT_TYPE) {}
183 
Visit__anonbe5bf2420111::ObjectTypeVisitor184         std::shared_ptr<UniverseObject> Visit(std::shared_ptr<Building> obj) const override
185         { m_type = OBJ_BUILDING; return obj; }
186 
Visit__anonbe5bf2420111::ObjectTypeVisitor187         std::shared_ptr<UniverseObject> Visit(std::shared_ptr<Fleet> obj) const override
188         { m_type = OBJ_FLEET; return obj; }
189 
Visit__anonbe5bf2420111::ObjectTypeVisitor190         std::shared_ptr<UniverseObject> Visit(std::shared_ptr<Planet> obj) const override
191         { m_type = OBJ_PLANET; return obj; }
192 
Visit__anonbe5bf2420111::ObjectTypeVisitor193         std::shared_ptr<UniverseObject> Visit(std::shared_ptr<Ship> obj) const override
194         { m_type = OBJ_SHIP; return obj; }
195 
Visit__anonbe5bf2420111::ObjectTypeVisitor196         std::shared_ptr<UniverseObject> Visit(std::shared_ptr<System> obj) const override
197         { m_type = OBJ_SYSTEM; return obj; }
198 
Visit__anonbe5bf2420111::ObjectTypeVisitor199         std::shared_ptr<UniverseObject> Visit(std::shared_ptr<Field> obj) const override
200         { m_type = OBJ_FIELD; return obj; }
201 
202         mutable UniverseObjectType m_type;
203     };
204 
GetMeterNameMap()205     const std::map<std::string, MeterType>& GetMeterNameMap() {
206         static const std::map<std::string, MeterType> meter_name_map{
207             {"Population",           METER_POPULATION},
208             {"TargetPopulation",     METER_TARGET_POPULATION},
209             {"Industry",             METER_INDUSTRY},
210             {"TargetIndustry",       METER_TARGET_INDUSTRY},
211             {"Research",             METER_RESEARCH},
212             {"TargetResearch",       METER_TARGET_RESEARCH},
213             {"Trade",                METER_TRADE},
214             {"TargetTrade",          METER_TARGET_TRADE},
215             {"Construction",         METER_CONSTRUCTION},
216             {"TargetConstruction",   METER_TARGET_CONSTRUCTION},
217             {"Happiness",            METER_HAPPINESS},
218             {"TargetHappiness",      METER_TARGET_HAPPINESS},
219             {"MaxFuel",              METER_MAX_FUEL},
220             {"Fuel",                 METER_FUEL},
221             {"MaxStructure",         METER_MAX_STRUCTURE},
222             {"Structure",            METER_STRUCTURE},
223             {"MaxShield",            METER_MAX_SHIELD},
224             {"Shield",               METER_SHIELD},
225             {"MaxDefense",           METER_MAX_DEFENSE},
226             {"Defense",              METER_DEFENSE},
227             {"MaxTroops",            METER_MAX_TROOPS},
228             {"Troops",               METER_TROOPS},
229             {"RebelTroops",          METER_REBEL_TROOPS},
230             {"Supply",               METER_SUPPLY},
231             {"MaxSupply",            METER_MAX_SUPPLY},
232             {"Stockpile",            METER_STOCKPILE},
233             {"MaxStockpile",         METER_MAX_STOCKPILE},
234             {"Stealth",              METER_STEALTH},
235             {"Detection",            METER_DETECTION},
236             {"Speed",                METER_SPEED},
237             {"Damage",               METER_CAPACITY},
238             {"Capacity",             METER_CAPACITY},
239             {"MaxCapacity",          METER_MAX_CAPACITY},
240             {"SecondaryStat",        METER_SECONDARY_STAT},
241             {"MaxSecondaryStat",     METER_MAX_SECONDARY_STAT},
242             {"Size",                 METER_SIZE}
243         };
244         return meter_name_map;
245     }
246 
247     // force early init to avoid threading issues later
248     std::map<std::string, MeterType> dummy = GetMeterNameMap();
249 }
250 
251 namespace ValueRef {
NameToMeter(const std::string & name)252 MeterType NameToMeter(const std::string& name) {
253     MeterType retval = INVALID_METER_TYPE;
254     auto it = GetMeterNameMap().find(name);
255     if (it != GetMeterNameMap().end())
256         retval = it->second;
257     return retval;
258 }
259 
MeterToName(MeterType meter)260 std::string MeterToName(MeterType meter) {
261     for (const auto& entry : GetMeterNameMap()) {
262         if (entry.second == meter)
263             return entry.first;
264     }
265     return "";
266 }
267 
ReconstructName(const std::vector<std::string> & property_name,ReferenceType ref_type,bool return_immediate_value)268 std::string ReconstructName(const std::vector<std::string>& property_name,
269                             ReferenceType ref_type,
270                             bool return_immediate_value)
271 {
272     std::string retval;
273 
274     if (return_immediate_value)
275         retval += "Value(";
276 
277     switch (ref_type) {
278     case SOURCE_REFERENCE:                    retval = "Source";          break;
279     case EFFECT_TARGET_REFERENCE:             retval = "Target";          break;
280     case EFFECT_TARGET_VALUE_REFERENCE:       retval = "Value";           break;
281     case CONDITION_LOCAL_CANDIDATE_REFERENCE: retval = "LocalCandidate";  break;
282     case CONDITION_ROOT_CANDIDATE_REFERENCE:  retval = "RootCandidate";   break;
283     case NON_OBJECT_REFERENCE:                retval = "";                break;
284     default:                                  retval = "?????";           break;
285     }
286 
287     if (ref_type != EFFECT_TARGET_VALUE_REFERENCE) {
288         for (const std::string& property_name_part : property_name) {
289             if (!retval.empty())
290                 retval += '.';
291             retval += property_name_part;
292         }
293     }
294 
295     if (return_immediate_value)
296         retval += ")";
297 
298     return retval;
299 }
300 
FormatedDescriptionPropertyNames(ReferenceType ref_type,const std::vector<std::string> & property_names,bool return_immediate_value)301 std::string FormatedDescriptionPropertyNames(ReferenceType ref_type,
302                                              const std::vector<std::string>& property_names,
303                                              bool return_immediate_value)
304 {
305     int num_references = property_names.size();
306     if (ref_type == NON_OBJECT_REFERENCE)
307         num_references--;
308     for (const std::string& property_name_part : property_names)
309         if (property_name_part.empty())
310              num_references--;
311     num_references = std::max(0, num_references);
312     std::string format_string;
313     switch (num_references) {
314     case 0: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE0"); break;
315     case 1: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE1"); break;
316     case 2: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE2"); break;
317     case 3: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE3"); break;
318     case 4: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE4"); break;
319     case 5: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE5"); break;
320     case 6: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE6"); break;
321     default:format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLEMANY"); break;
322     }
323 
324     boost::format formatter = FlexibleFormat(format_string);
325 
326     switch (ref_type) {
327     case SOURCE_REFERENCE:                    formatter % UserString("DESC_VAR_SOURCE");          break;
328     case EFFECT_TARGET_REFERENCE:             formatter % UserString("DESC_VAR_TARGET");          break;
329     case EFFECT_TARGET_VALUE_REFERENCE:       formatter % UserString("DESC_VAR_VALUE");           break;
330     case CONDITION_LOCAL_CANDIDATE_REFERENCE: formatter % UserString("DESC_VAR_LOCAL_CANDIDATE"); break;
331     case CONDITION_ROOT_CANDIDATE_REFERENCE:  formatter % UserString("DESC_VAR_ROOT_CANDIDATE");  break;
332     case NON_OBJECT_REFERENCE:                                                                    break;
333     default:                                  formatter % "???";                                  break;
334     }
335 
336     for (const std::string& property_name_part : property_names) {
337         if (property_name_part.empty())  // apparently is empty for a EFFECT_TARGET_VALUE_REFERENCE
338             continue;
339         std::string stringtable_key("DESC_VAR_" + boost::to_upper_copy(property_name_part));
340         formatter % UserString(stringtable_key);
341     }
342 
343     std::string retval = boost::io::str(formatter);
344     //std::cerr << "ret: " << retval << std::endl;
345     return retval;
346 }
347 
ComplexVariableDescription(const std::vector<std::string> & property_names,const ValueRef<int> * int_ref1,const ValueRef<int> * int_ref2,const ValueRef<int> * int_ref3,const ValueRef<std::string> * string_ref1,const ValueRef<std::string> * string_ref2)348 std::string ComplexVariableDescription(const std::vector<std::string>& property_names,
349                                        const ValueRef<int>* int_ref1,
350                                        const ValueRef<int>* int_ref2,
351                                        const ValueRef<int>* int_ref3,
352                                        const ValueRef<std::string>* string_ref1,
353                                        const ValueRef<std::string>* string_ref2)
354 {
355     if (property_names.empty()) {
356         ErrorLogger() << "ComplexVariableDescription passed empty property names?!";
357         return "";
358     }
359 
360     std::string stringtable_key("DESC_VAR_" + boost::to_upper_copy(property_names.back()));
361     if (!UserStringExists(stringtable_key))
362         return "";
363 
364     boost::format formatter = FlexibleFormat(UserString(stringtable_key));
365 
366     if (int_ref1)
367         formatter % int_ref1->Description();
368     if (int_ref2)
369         formatter % int_ref2->Description();
370     if (int_ref3)
371         formatter % int_ref3->Description();
372     if (string_ref1)
373         formatter % string_ref1->Description();
374     if (string_ref2)
375         formatter % string_ref2->Description();
376 
377     return boost::io::str(formatter);
378 }
379 
ComplexVariableDump(const std::vector<std::string> & property_names,const ValueRef<int> * int_ref1,const ValueRef<int> * int_ref2,const ValueRef<int> * int_ref3,const ValueRef<std::string> * string_ref1,const ValueRef<std::string> * string_ref2)380 std::string ComplexVariableDump(const std::vector<std::string>& property_names,
381                                 const ValueRef<int>* int_ref1,
382                                 const ValueRef<int>* int_ref2,
383                                 const ValueRef<int>* int_ref3,
384                                 const ValueRef<std::string>* string_ref1,
385                                 const ValueRef<std::string>* string_ref2)
386 {
387     std::string retval;
388     if (property_names.empty()) {
389         ErrorLogger() << "ComplexVariableDump passed empty property names?!";
390         return "ComplexVariable";
391     } else {
392         retval += property_names.back();
393     }
394 
395     // TODO: Depending on the property name, the parameter names will vary.
396     //       Need to handle each case correctly, in order for the Dumped
397     //       text to be parsable as the correct ComplexVariable.
398 
399     if (int_ref1)
400         retval += " int1 = " + int_ref1->Dump();
401     if (int_ref2)
402         retval += " int2 = " + int_ref2->Dump();
403     if (int_ref3)
404         retval += " int3 = " + int_ref3->Dump();
405     if (string_ref1)
406         retval += " string1 = " + string_ref1->Dump();
407     if (string_ref2)
408         retval += " string2 = " + string_ref2->Dump();
409 
410     return retval;
411 }
412 
StatisticDescription(StatisticType stat_type,const std::string & value_desc,const std::string & condition_desc)413 std::string StatisticDescription(StatisticType stat_type,
414                                  const std::string& value_desc,
415                                  const std::string& condition_desc)
416 {
417     std::string stringtable_key("DESC_VAR_" + boost::to_upper_copy(
418         boost::lexical_cast<std::string>(stat_type)));
419 
420     if (UserStringExists(stringtable_key)) {
421         boost::format formatter = FlexibleFormat(stringtable_key);
422         formatter % value_desc % condition_desc;
423         return boost::io::str(formatter);
424     }
425 
426     return UserString("DESC_VAR_STATISITIC");
427 }
428 
429 ///////////////////////////////////////////////////////////
430 // Constant                                              //
431 ///////////////////////////////////////////////////////////
432 template <>
Description() const433 std::string Constant<int>::Description() const
434 {
435     if (std::abs(m_value) < 1000)
436         return std::to_string(m_value);
437     return DoubleToString(m_value, 3, false);
438 }
439 
440 template <>
Description() const441 std::string Constant<double>::Description() const
442 { return DoubleToString(m_value, 3, false); }
443 
444 template <>
Description() const445 std::string Constant<std::string>::Description() const
446 {
447     if (m_value == "CurrentContent")
448         return m_top_level_content;
449     return m_value;
450 }
451 
452 template <>
Dump(unsigned short ntabs) const453 std::string Constant<PlanetSize>::Dump(unsigned short ntabs) const
454 {
455     switch (m_value) {
456     case SZ_TINY:       return "Tiny";
457     case SZ_SMALL:      return "Small";
458     case SZ_MEDIUM:     return "Medium";
459     case SZ_LARGE:      return "Large";
460     case SZ_HUGE:       return "Huge";
461     case SZ_ASTEROIDS:  return "Asteroids";
462     case SZ_GASGIANT:   return "GasGiant";
463     default:            return "?";
464     }
465 }
466 
467 template <>
Dump(unsigned short ntabs) const468 std::string Constant<PlanetType>::Dump(unsigned short ntabs) const
469 {
470     switch (m_value) {
471     case PT_SWAMP:      return "Swamp";
472     case PT_TOXIC:      return "Toxic";
473     case PT_INFERNO:    return "Inferno";
474     case PT_RADIATED:   return "Radiated";
475     case PT_BARREN:     return "Barren";
476     case PT_TUNDRA:     return "Tundra";
477     case PT_DESERT:     return "Desert";
478     case PT_TERRAN:     return "Terran";
479     case PT_OCEAN:      return "Ocean";
480     case PT_ASTEROIDS:  return "Asteroids";
481     case PT_GASGIANT:   return "GasGiant";
482     default:            return "?";
483     }
484 }
485 
486 template <>
Dump(unsigned short ntabs) const487 std::string Constant<PlanetEnvironment>::Dump(unsigned short ntabs) const
488 {
489     switch (m_value) {
490     case PE_UNINHABITABLE:  return "Uninhabitable";
491     case PE_HOSTILE:        return "Hostile";
492     case PE_POOR:           return "Poor";
493     case PE_ADEQUATE:       return "Adequate";
494     case PE_GOOD:           return "Good";
495     default:                return "?";
496     }
497 }
498 
499 template <>
Dump(unsigned short ntabs) const500 std::string Constant<UniverseObjectType>::Dump(unsigned short ntabs) const
501 {
502     switch (m_value) {
503     case OBJ_BUILDING:      return "Building";
504     case OBJ_SHIP:          return "Ship";
505     case OBJ_FLEET:         return "Fleet";
506     case OBJ_PLANET:        return "Planet";
507     case OBJ_POP_CENTER:    return "PopulationCenter";
508     case OBJ_PROD_CENTER:   return "ProductionCenter";
509     case OBJ_SYSTEM:        return "System";
510     case OBJ_FIELD:         return "Field";
511     default:                return "?";
512     }
513 }
514 
515 template <>
Dump(unsigned short ntabs) const516 std::string Constant<StarType>::Dump(unsigned short ntabs) const
517 {
518     switch (m_value) {
519     case STAR_BLUE:     return "Blue";
520     case STAR_WHITE:    return "White";
521     case STAR_YELLOW:   return "Yellow";
522     case STAR_ORANGE:   return "Orange";
523     case STAR_RED:      return "Red";
524     case STAR_NEUTRON:  return "Neutron";
525     case STAR_BLACK:    return "BlackHole";
526     case STAR_NONE:     return "NoStar";
527     default:            return "Unknown";
528     }
529 }
530 
531 template <>
Dump(unsigned short ntabs) const532 std::string Constant<Visibility>::Dump(unsigned short ntabs) const
533 {
534     switch (m_value) {
535     case VIS_NO_VISIBILITY:     return "Invisible";
536     case VIS_BASIC_VISIBILITY:  return "Basic";
537     case VIS_PARTIAL_VISIBILITY:return "Partial";
538     case VIS_FULL_VISIBILITY:   return "Full";
539     default:                    return "Unknown";
540     }
541 }
542 
543 template <>
Dump(unsigned short ntabs) const544 std::string Constant<int>::Dump(unsigned short ntabs) const
545 { return std::to_string(m_value); }
546 
547 template <>
Dump(unsigned short ntabs) const548 std::string Constant<double>::Dump(unsigned short ntabs) const
549 { return std::to_string(m_value); }
550 
551 template <>
Dump(unsigned short ntabs) const552 std::string Constant<std::string>::Dump(unsigned short ntabs) const
553 { return "\"" + Description() + "\""; }
554 
555 template <>
Eval(const ScriptingContext & context) const556 std::string Constant<std::string>::Eval(const ScriptingContext& context) const
557 {
558     if (m_value == "CurrentContent")
559         return m_top_level_content;
560     return m_value;
561 }
562 
563 ///////////////////////////////////////////////////////////
564 // Variable                                              //
565 ///////////////////////////////////////////////////////////
566 #define IF_CURRENT_VALUE(T)                                            \
567 if (m_ref_type == EFFECT_TARGET_VALUE_REFERENCE) {                     \
568     if (context.current_value.empty())                                 \
569         throw std::runtime_error(                                      \
570             "Variable<" #T ">::Eval(): Value could not be evaluated, " \
571             "because no current value was provided.");                 \
572     try {                                                              \
573         return boost::any_cast<T>(context.current_value);              \
574     } catch (const boost::bad_any_cast&) {                             \
575         throw std::runtime_error(                                      \
576             "Variable<" #T ">::Eval(): Value could not be evaluated, " \
577             "because the provided current value is not an " #T ".");   \
578     }                                                                  \
579 }
580 
581 #define LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(T)                         \
582 ErrorLogger() << "Variable<" #T ">::Eval unrecognized object "         \
583                  "property: "                                          \
584               << TraceReference(m_property_name, m_ref_type, context); \
585 if (context.source)                                                    \
586     ErrorLogger() << "source: " << context.source->ObjectType() << " " \
587                   << context.source->ID() << " ( "                     \
588                   << context.source->Name() << " ) ";                  \
589 else                                                                   \
590     ErrorLogger() << "source (none)";
591 
592 template <>
Eval(const ScriptingContext & context) const593 PlanetSize Variable<PlanetSize>::Eval(const ScriptingContext& context) const
594 {
595     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
596 
597     IF_CURRENT_VALUE(PlanetSize)
598 
599     auto object = FollowReference(m_property_name.begin(), m_property_name.end(),
600                                   m_ref_type, context);
601     if (!object) {
602         ErrorLogger() << "Variable<PlanetSize>::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context);
603         return INVALID_PLANET_SIZE;
604     }
605 
606     std::function<PlanetSize (const Planet&)> planet_property{nullptr};
607 
608     if (property_name == "PlanetSize")
609         planet_property = &Planet::Size;
610     else if (property_name == "NextLargerPlanetSize")
611         planet_property = &Planet::NextLargerPlanetSize;
612     else if (property_name == "NextSmallerPlanetSize")
613         planet_property = &Planet::NextSmallerPlanetSize;
614 
615     if (planet_property) {
616         if (auto p = std::dynamic_pointer_cast<const Planet>(object))
617             return planet_property(*p);
618         return INVALID_PLANET_SIZE;
619     }
620 
621     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(PlanetSize)
622 
623     return INVALID_PLANET_SIZE;
624 }
625 
626 template <>
Eval(const ScriptingContext & context) const627 PlanetType Variable<PlanetType>::Eval(const ScriptingContext& context) const
628 {
629     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
630 
631     IF_CURRENT_VALUE(PlanetType)
632 
633     auto object = FollowReference(m_property_name.begin(), m_property_name.end(),
634                                   m_ref_type, context);
635     if (!object) {
636         ErrorLogger() << "Variable<PlanetType>::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context);
637         return INVALID_PLANET_TYPE;
638     }
639 
640     std::function<PlanetType (const Planet&)> planet_property{nullptr};
641 
642 #if BOOST_VERSION >= 106000
643     using boost::placeholders::_1;
644 #endif
645 
646     if (property_name == "PlanetType")
647         planet_property = &Planet::Type;
648     else if (property_name == "OriginalType")
649         planet_property = &Planet::OriginalType;
650     else if (property_name == "NextCloserToOriginalPlanetType")
651         planet_property = &Planet::NextCloserToOriginalPlanetType;
652     else if (property_name == "NextBetterPlanetType")
653         planet_property = boost::bind(&Planet::NextBetterPlanetTypeForSpecies, _1, "");
654     else if (property_name == "ClockwiseNextPlanetType")
655         planet_property = &Planet::ClockwiseNextPlanetType;
656     else if (property_name == "CounterClockwiseNextPlanetType")
657         planet_property = &Planet::CounterClockwiseNextPlanetType;
658 
659     if (planet_property) {
660         if (auto p = std::dynamic_pointer_cast<const Planet>(object))
661             return planet_property(*p);
662         return INVALID_PLANET_TYPE;
663     }
664 
665     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(PlanetType)
666 
667     return INVALID_PLANET_TYPE;
668 }
669 
670 template <>
Eval(const ScriptingContext & context) const671 PlanetEnvironment Variable<PlanetEnvironment>::Eval(const ScriptingContext& context) const
672 {
673     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
674 
675     IF_CURRENT_VALUE(PlanetEnvironment)
676 
677     if (property_name == "PlanetEnvironment") {
678         auto object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context);
679         if (!object) {
680             ErrorLogger() << "Variable<PlanetEnvironment>::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context);
681             return INVALID_PLANET_ENVIRONMENT;
682         }
683         if (auto p = std::dynamic_pointer_cast<const Planet>(object))
684             return p->EnvironmentForSpecies();
685 
686         return INVALID_PLANET_ENVIRONMENT;
687     }
688 
689     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(PlanetEnvironment)
690 
691     return INVALID_PLANET_ENVIRONMENT;
692 }
693 
694 template <>
Eval(const ScriptingContext & context) const695 UniverseObjectType Variable<UniverseObjectType>::Eval(const ScriptingContext& context) const
696 {
697     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
698 
699     IF_CURRENT_VALUE(UniverseObjectType)
700 
701     if (property_name == "ObjectType") {
702         auto object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context);
703         if (!object) {
704             ErrorLogger() << "Variable<UniverseObjectType>::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context);
705             return INVALID_UNIVERSE_OBJECT_TYPE;
706         }
707         ObjectTypeVisitor v;
708         if (object->Accept(v))
709             return v.m_type;
710         else if (std::dynamic_pointer_cast<const PopCenter>(object))
711             return OBJ_POP_CENTER;
712         else if (std::dynamic_pointer_cast<const ResourceCenter>(object))
713             return OBJ_PROD_CENTER;
714 
715         return INVALID_UNIVERSE_OBJECT_TYPE;
716     }
717 
718     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(UniverseObjectType)
719 
720     return INVALID_UNIVERSE_OBJECT_TYPE;
721 }
722 
723 template <>
Eval(const ScriptingContext & context) const724 StarType Variable<StarType>::Eval(const ScriptingContext& context) const
725 {
726     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
727 
728     IF_CURRENT_VALUE(StarType)
729 
730     auto object = FollowReference(m_property_name.begin(), m_property_name.end(),
731                                   m_ref_type, context);
732     if (!object) {
733         ErrorLogger() << "Variable<StarType>::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context);
734         return INVALID_STAR_TYPE;
735     }
736 
737     std::function<StarType (const System&)> system_property{nullptr};
738 
739     if (property_name == "StarType")
740         system_property = &System::GetStarType;
741     else if (property_name == "NextOlderStarType")
742         system_property = &System::NextOlderStarType;
743     else if (property_name == "NextYoungerStarType")
744         system_property = &System::NextYoungerStarType;
745 
746     if (system_property) {
747         if (auto s = std::dynamic_pointer_cast<const System>(object))
748             return system_property(*s);
749         return INVALID_STAR_TYPE;
750     }
751 
752     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(StarType)
753 
754     return INVALID_STAR_TYPE;
755 }
756 
757 template <>
Eval(const ScriptingContext & context) const758 Visibility Variable<Visibility>::Eval(const ScriptingContext& context) const
759 {
760     IF_CURRENT_VALUE(Visibility)
761 
762     // As of this writing, there are no properties of objects that directly
763     // return a Visibility, as it will normally need to be queried for a
764     // particular empire
765 
766     ErrorLogger() << "Variable<Visibility>::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context);
767 
768     return INVALID_VISIBILITY;
769 }
770 
771 template <>
Eval(const ScriptingContext & context) const772 double Variable<double>::Eval(const ScriptingContext& context) const
773 {
774     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
775 
776     IF_CURRENT_VALUE(float)
777 
778     if (m_ref_type == NON_OBJECT_REFERENCE) {
779         if ((property_name == "UniverseCentreX") |
780             (property_name == "UniverseCentreY"))
781         {
782             return GetUniverse().UniverseWidth() / 2;
783         } else if (property_name == "UniverseWidth") {
784             return GetUniverse().UniverseWidth();
785         }
786 
787         // add more non-object reference double functions here
788 
789         LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(float)
790 
791         return 0.0;
792     }
793 
794     auto object = FollowReference(m_property_name.begin(), m_property_name.end(),
795                                   m_ref_type, context);
796     if (!object) {
797         LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(float)
798 
799         return 0.0;
800     }
801 
802     MeterType meter_type = NameToMeter(property_name);
803     if (object && meter_type != INVALID_METER_TYPE) {
804         if (auto* m = object->GetMeter(meter_type))
805             return m_return_immediate_value ? m->Current() : m->Initial();
806         return 0.0;
807 
808     } else if (property_name == "X") {
809         return object->X();
810 
811     } else if (property_name == "Y") {
812         return object->Y();
813 
814     }
815 
816     std::function<double (const Planet&)> planet_property{nullptr};
817 
818     if (property_name == "SizeAsDouble")
819         planet_property = &Planet::Size;
820     else if (property_name == "HabitableSize")
821         planet_property = &Planet::HabitableSize;
822     else if (property_name == "DistanceFromOriginalType")
823         planet_property = &Planet::DistanceFromOriginalType;
824 
825     if (planet_property) {
826         if (auto planet = std::dynamic_pointer_cast<const Planet>(object))
827             return planet_property(*planet);
828         return 0.0;
829 
830     }
831 
832     if (property_name == "CombatBout") {
833         return context.combat_info.bout;
834 
835     } else if (property_name == "CurrentTurn") {
836         return CurrentTurn();
837 
838     } else if (property_name == "Attack") {
839         if (auto fleet = std::dynamic_pointer_cast<const Fleet>(object))
840             return fleet->Damage();
841         if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
842             return ship->TotalWeaponsDamage();
843         if (auto fighter = std::dynamic_pointer_cast<const Fighter>(object))
844             return fighter->Damage();
845         return 0.0;
846 
847     } else if (property_name == "PropagatedSupplyRange") {
848         const auto& ranges = GetSupplyManager().PropagatedSupplyRanges();
849         auto range_it = ranges.find(object->SystemID());
850         if (range_it == ranges.end())
851             return 0.0;
852         return range_it->second;
853 
854     } else if (property_name == "PropagatedSupplyDistance") {
855         const auto& ranges = GetSupplyManager().PropagatedSupplyDistances();
856         auto range_it = ranges.find(object->SystemID());
857         if (range_it == ranges.end())
858             return 0.0;
859         return range_it->second;
860     }
861 
862     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(float)
863 
864     return 0.0;
865 }
866 
867 template <>
Eval(const ScriptingContext & context) const868 int Variable<int>::Eval(const ScriptingContext& context) const
869 {
870     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
871 
872     IF_CURRENT_VALUE(int)
873 
874     if (m_ref_type == NON_OBJECT_REFERENCE) {
875         if (property_name == "CombatBout")
876             return context.combat_info.bout;
877         if (property_name == "CurrentTurn")
878             return CurrentTurn();
879         if (property_name == "GalaxySize")
880             return GetGalaxySetupData().GetSize();
881         if (property_name == "GalaxyShape")
882             return static_cast<int>(GetGalaxySetupData().GetShape());
883         if (property_name == "GalaxyAge")
884             return static_cast<int>(GetGalaxySetupData().GetAge());
885         if (property_name == "GalaxyStarlaneFrequency")
886             return static_cast<int>(GetGalaxySetupData().GetStarlaneFreq());
887         if (property_name == "GalaxyPlanetDensity")
888             return static_cast<int>(GetGalaxySetupData().GetPlanetDensity());
889         if (property_name == "GalaxySpecialFrequency")
890             return static_cast<int>(GetGalaxySetupData().GetSpecialsFreq());
891         if (property_name == "GalaxyMonsterFrequency")
892             return static_cast<int>(GetGalaxySetupData().GetMonsterFreq());
893         if (property_name == "GalaxyNativeFrequency")
894             return static_cast<int>(GetGalaxySetupData().GetNativeFreq());
895         if (property_name == "GalaxyMaxAIAggression")
896             return static_cast<int>(GetGalaxySetupData().GetAggression());
897 
898         // non-object values passed by abuse of context.current_value
899         if (property_name == "UsedInDesignID") {
900             // check if an int was passed as the current_value, as would be
901             // done when evaluating a ValueRef for the cost or production
902             // time of a part or hull in a ship design. this should be the id
903             // of the design.
904             try {
905                 return boost::any_cast<int>(context.current_value);
906             } catch (...) {
907                 ErrorLogger() << "Variable<int>::Eval could get ship design id for property: " << TraceReference(m_property_name, m_ref_type, context);
908             }
909             return 0;
910         }
911 
912         // add more non-object reference int functions here
913 
914         LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(int)
915 
916         return 0;
917     }
918 
919     auto object = FollowReference(m_property_name.begin(), m_property_name.end(),
920                                   m_ref_type, context);
921     if (!object) {
922         LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(int)
923 
924         return 0;
925     }
926 
927     if (property_name == "Owner") {
928         return object->Owner();
929     }
930     else if (property_name == "SystemID") {
931         return object->SystemID();
932 
933     }
934     else if (property_name == "ContainerID") {
935         return object->ContainerObjectID();
936 
937     }
938     else if (property_name == "SupplyingEmpire") {
939         return GetSupplyManager().EmpireThatCanSupplyAt(object->SystemID());
940     }
941     else if (property_name == "ID") {
942         return object->ID();
943     }
944     else if (property_name == "CreationTurn") {
945         return object->CreationTurn();
946     }
947     else if (property_name == "Age") {
948         return object->AgeInTurns();
949 
950     }
951 
952     std::function<int (const Ship&)> ship_property{nullptr};
953 
954     if (property_name == "ArrivedOnTurn")
955         ship_property = &Ship::ArrivedOnTurn;
956     else if (property_name == "LastTurnActiveInBattle")
957         ship_property = &Ship::LastTurnActiveInCombat;
958     else if (property_name == "LastTurnResupplied")
959         ship_property = &Ship::LastResuppliedOnTurn;
960 
961     if (ship_property) {
962         if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
963             return ship_property(*ship);
964         return INVALID_GAME_TURN;
965     }
966 
967     std::function<int (const Fleet&)> fleet_property{nullptr};
968 
969     if (property_name == "FinalDestinationID")
970         fleet_property = &Fleet::FinalDestinationID;
971     else if (property_name == "NextSystemID")
972         fleet_property = &Fleet::NextSystemID;
973     else if (property_name == "PreviousSystemID")
974         fleet_property = &Fleet::PreviousSystemID;
975     else if (property_name == "ArrivalStarlaneID")
976         fleet_property = &Fleet::ArrivalStarlane;
977 
978     if (fleet_property) {
979         if (auto fleet = std::dynamic_pointer_cast<const Fleet>(object))
980             return fleet_property(*fleet);
981         return INVALID_OBJECT_ID;
982     }
983 
984     std::function<int (const Planet&)> planet_property{nullptr};
985 
986     if (property_name == "LastTurnAttackedByShip")
987         planet_property = &Planet::LastTurnAttackedByShip;
988     else if (property_name == "LastTurnColonized")
989         planet_property = &Planet::LastTurnColonized;
990     else if (property_name == "LastTurnConquered")
991         planet_property = &Planet::LastTurnConquered;
992 
993     if (planet_property) {
994         if (auto planet = std::dynamic_pointer_cast<const Planet>(object))
995             return planet_property(*planet);
996         return INVALID_GAME_TURN;
997     }
998 
999     if (property_name == "TurnsSinceFocusChange") {
1000         if (auto planet = std::dynamic_pointer_cast<const Planet>(object))
1001             return planet->TurnsSinceFocusChange();
1002         return 0;
1003 
1004     }
1005     else if (property_name == "ProducedByEmpireID") {
1006         if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
1007             return ship->ProducedByEmpireID();
1008         else if (auto building = std::dynamic_pointer_cast<const Building>(object))
1009             return building->ProducedByEmpireID();
1010         return ALL_EMPIRES;
1011 
1012     }
1013     else if (property_name == "DesignID") {
1014         if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
1015             return ship->DesignID();
1016         return INVALID_DESIGN_ID;
1017 
1018     }
1019     else if (property_name == "SpeciesID") {
1020         if (auto planet = std::dynamic_pointer_cast<const Planet>(object))
1021             return GetSpeciesManager().GetSpeciesID(planet->SpeciesName());
1022         else if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
1023             return GetSpeciesManager().GetSpeciesID(ship->SpeciesName());
1024         return -1;
1025 
1026     }
1027     else if (property_name == "FleetID") {
1028         if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
1029             return ship->FleetID();
1030         else if (auto fleet = std::dynamic_pointer_cast<const Fleet>(object))
1031             return fleet->ID();
1032         return INVALID_OBJECT_ID;
1033 
1034     }
1035     else if (property_name == "PlanetID") {
1036         if (auto building = std::dynamic_pointer_cast<const Building>(object))
1037             return building->PlanetID();
1038         else if (auto planet = std::dynamic_pointer_cast<const Planet>(object))
1039             return planet->ID();
1040         return INVALID_OBJECT_ID;
1041 
1042     }
1043     else if (property_name == "NearestSystemID") {
1044         if (object->SystemID() != INVALID_OBJECT_ID)
1045             return object->SystemID();
1046         return GetPathfinder()->NearestSystemTo(object->X(), object->Y());
1047 
1048     }
1049     else if (property_name == "NumShips") {
1050         if (auto fleet = std::dynamic_pointer_cast<const Fleet>(object))
1051             return fleet->NumShips();
1052         return 0;
1053 
1054     }
1055     else if (property_name == "NumStarlanes") {
1056         if (auto system = std::dynamic_pointer_cast<const System>(object))
1057             return system->NumStarlanes();
1058         return 0;
1059 
1060     }
1061     else if (property_name == "LastTurnBattleHere") {
1062         if (auto const_system = std::dynamic_pointer_cast<const System>(object))
1063             return const_system->LastTurnBattleHere();
1064         else if (auto system = context.ContextObjects().get<System>(object->SystemID()))
1065             return system->LastTurnBattleHere();
1066         return INVALID_GAME_TURN;
1067 
1068     }
1069     else if (property_name == "Orbit") {
1070         if (auto system = context.ContextObjects().get<System>(object->SystemID()))
1071             return system->OrbitOfPlanet(object->ID());
1072         return -1;
1073 
1074     }
1075     else if (property_name == "ETA") {
1076         if (auto fleet = std::dynamic_pointer_cast<const Fleet>(object))
1077             return fleet->ETA().first;
1078         return 0;
1079 
1080     }
1081     else if (property_name == "NumSpecials") {
1082         return object->Specials().size();
1083 
1084     }
1085     else if (property_name == "LaunchedFrom") {
1086         if (auto fighter = std::dynamic_pointer_cast<const Fighter>(object))
1087             return fighter->LaunchedFrom();
1088         return INVALID_OBJECT_ID;
1089     }
1090 
1091     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(int)
1092 
1093     return 0;
1094 }
1095 
1096 template <>
Eval(const ScriptingContext & context) const1097 std::vector<std::string> Variable<std::vector<std::string>>::Eval(
1098     const ScriptingContext& context) const
1099 {
1100     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
1101 
1102     IF_CURRENT_VALUE(std::vector<std::string>)
1103 
1104     if (m_ref_type == NON_OBJECT_REFERENCE) {
1105         // add more non-object reference string vector functions here
1106         LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::vector<std::string>)
1107 
1108         return {};
1109     }
1110 
1111     auto object = FollowReference(m_property_name.begin(), m_property_name.end(),
1112                                   m_ref_type, context);
1113     if (!object) {
1114         LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::vector<std::string>)
1115 
1116         return {};
1117     }
1118 
1119     if (property_name == "Tags") {
1120         std::vector<std::string> retval;
1121         for (auto tag : object->Tags())
1122             retval.push_back(tag);
1123         return retval;
1124     }
1125     else if (property_name == "Specials") {
1126         std::vector<std::string> retval;
1127         for (auto spec : object->Specials())
1128             retval.push_back(spec.first);
1129         return retval;
1130     }
1131     else if (property_name == "AvailableFoci") {
1132         if (auto planet = std::dynamic_pointer_cast<const Planet>(object))
1133             return planet->AvailableFoci();
1134         return {};
1135     }
1136     else if (property_name == "Parts") {
1137         if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
1138             if (const ShipDesign* design = ship->Design())
1139                 return design->Parts();
1140         return {};
1141     }
1142 
1143     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::vector<std::string>)
1144 
1145     return {};
1146 }
1147 
1148 template <>
Eval(const ScriptingContext & context) const1149 std::string Variable<std::string>::Eval(const ScriptingContext& context) const
1150 {
1151     const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back();
1152 
1153     IF_CURRENT_VALUE(std::string)
1154 
1155     if (m_ref_type == NON_OBJECT_REFERENCE) {
1156         if (property_name == "GalaxySeed")
1157             return GetGalaxySetupData().GetSeed();
1158 
1159         // add more non-object reference string functions here
1160         LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::string)
1161 
1162         return "";
1163     }
1164 
1165     auto object = FollowReference(m_property_name.begin(), m_property_name.end(),
1166                                   m_ref_type, context);
1167     if (!object) {
1168         LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::string)
1169 
1170         return "";
1171     }
1172 
1173     if (property_name == "Name") {
1174         return object->Name();
1175 
1176     } else if (property_name == "OwnerName") {
1177         int owner_empire_id = object->Owner();
1178         if (Empire* empire = GetEmpire(owner_empire_id))
1179             return empire->Name();
1180         return "";
1181 
1182     } else if (property_name == "TypeName") {
1183         return boost::lexical_cast<std::string>(object->ObjectType());
1184 
1185     }
1186 
1187     std::function<std::string (const Empire&)> empire_property{nullptr};
1188 
1189     if (property_name == "OwnerLeastExpensiveEnqueuedTech")
1190         empire_property = &Empire::LeastExpensiveEnqueuedTech;
1191     else if (property_name == "OwnerMostExpensiveEnqueuedTech")
1192         empire_property = &Empire::MostExpensiveEnqueuedTech;
1193     else if (property_name == "OwnerMostRPCostLeftEnqueuedTech")
1194         empire_property = &Empire::MostRPCostLeftEnqueuedTech;
1195     else if (property_name == "OwnerMostRPSpentEnqueuedTech")
1196         empire_property = &Empire::MostRPSpentEnqueuedTech;
1197     else if (property_name == "OwnerTopPriorityEnqueuedTech")
1198         empire_property = &Empire::TopPriorityEnqueuedTech;
1199 
1200     if (empire_property) {
1201         const Empire* empire = GetEmpire(object->Owner());
1202         if (!empire)
1203             return "";
1204         return empire_property(*empire);
1205     }
1206 
1207     if (property_name == "Species") {
1208         if (auto planet = std::dynamic_pointer_cast<const Planet>(object))
1209             return planet->SpeciesName();
1210         else if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
1211             return ship->SpeciesName();
1212         else if (auto fighter = std::dynamic_pointer_cast<const Fighter>(object))
1213             return fighter->SpeciesName();
1214         return "";
1215 
1216     } else if (property_name == "Hull") {
1217         if (auto ship = std::dynamic_pointer_cast<const Ship>(object))
1218             if (const ShipDesign* design = ship->Design())
1219                 return design->Hull();
1220         return "";
1221 
1222     } else if (property_name == "FieldType") {
1223         if (auto field = std::dynamic_pointer_cast<const Field>(object))
1224             return field->FieldTypeName();
1225         return "";
1226 
1227     } else if (property_name == "BuildingType") {
1228         if (auto building = std::dynamic_pointer_cast<const Building>(object))
1229             return building->BuildingTypeName();
1230         return "";
1231 
1232     } else if (property_name == "Focus") {
1233         if (auto planet = std::dynamic_pointer_cast<const Planet>(object))
1234             return planet->Focus();
1235         return "";
1236 
1237     } else if (property_name == "PreferredFocus") {
1238         const Species* species = nullptr;
1239         if (auto planet = std::dynamic_pointer_cast<const Planet>(object)) {
1240             species = GetSpecies(planet->SpeciesName());
1241         } else if (auto ship = std::dynamic_pointer_cast<const Ship>(object)) {
1242             species = GetSpecies(ship->SpeciesName());
1243         }
1244         if (species)
1245             return species->PreferredFocus();
1246         return "";
1247 
1248     }
1249 
1250     LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::string)
1251 
1252     return "";
1253 }
1254 
1255 #undef IF_CURRENT_VALUE
1256 
1257 ///////////////////////////////////////////////////////////
1258 // Statistic                                             //
1259 ///////////////////////////////////////////////////////////
1260 template <>
Eval(const ScriptingContext & context) const1261 double Statistic<double>::Eval(const ScriptingContext& context) const
1262 {
1263     Condition::ObjectSet condition_matches;
1264     GetConditionMatches(context, condition_matches, m_sampling_condition.get());
1265 
1266     // these two statistic types don't depend on the object property values,
1267     // so can be evaluated without getting those values.
1268     if (m_stat_type == COUNT)
1269         return static_cast<double>(condition_matches.size());
1270     if (m_stat_type == IF)
1271         return condition_matches.empty() ? 0.0 : 1.0;
1272 
1273     // evaluate property for each condition-matched object
1274     std::map<std::shared_ptr<const UniverseObject>, double> object_property_values;
1275     GetObjectPropertyValues(context, condition_matches, object_property_values);
1276 
1277     return ReduceData(object_property_values);
1278 }
1279 
1280 template <>
Eval(const ScriptingContext & context) const1281 int Statistic<int>::Eval(const ScriptingContext& context) const
1282 {
1283     Condition::ObjectSet condition_matches;
1284     GetConditionMatches(context, condition_matches, m_sampling_condition.get());
1285 
1286     // these two statistic types don't depend on the object property values,
1287     // so can be evaluated without getting those values.
1288     if (m_stat_type == COUNT)
1289         return static_cast<int>(condition_matches.size());
1290     if (m_stat_type == IF)
1291         return condition_matches.empty() ? 0 : 1;
1292 
1293     // evaluate property for each condition-matched object
1294     std::map<std::shared_ptr<const UniverseObject>, int> object_property_values;
1295     GetObjectPropertyValues(context, condition_matches, object_property_values);
1296 
1297     return ReduceData(object_property_values);
1298 }
1299 
1300 template <>
Eval(const ScriptingContext & context) const1301 std::string Statistic<std::string>::Eval(const ScriptingContext& context) const
1302 {
1303     Condition::ObjectSet condition_matches;
1304     GetConditionMatches(context, condition_matches, m_sampling_condition.get());
1305 
1306     if (condition_matches.empty())
1307         return "";
1308 
1309     // special case for IF statistic... return a non-empty string for true
1310     if (m_stat_type == IF)
1311         return " "; // not an empty string
1312 
1313     // todo: consider allowing MAX and MIN using string sorting?
1314 
1315     // the only other statistic that can be computed on non-number property
1316     // types and that is itself of a non-number type is the most common value
1317     if (m_stat_type != MODE) {
1318         ErrorLogger() << "Statistic<std::string>::Eval has invalid statistic type: "
1319                       << m_stat_type;
1320         return "";
1321     }
1322 
1323     // evaluate property for each condition-matched object
1324     std::map<std::shared_ptr<const UniverseObject>, std::string> object_property_values;
1325     GetObjectPropertyValues(context, condition_matches, object_property_values);
1326 
1327     // count number of each result, tracking which has the most occurances
1328     std::map<std::string, unsigned int> histogram;
1329     auto most_common_property_value_it = histogram.begin();
1330     unsigned int max_seen(0);
1331 
1332     for (const auto& entry : object_property_values) {
1333         const std::string& property_value = entry.second;
1334 
1335         auto hist_it = histogram.find(property_value);
1336         if (hist_it == histogram.end())
1337             hist_it = histogram.insert({property_value, 0}).first;
1338         unsigned int& num_seen = hist_it->second;
1339 
1340         num_seen++;
1341 
1342         if (num_seen > max_seen) {
1343             most_common_property_value_it = hist_it;
1344             max_seen = num_seen;
1345         }
1346     }
1347 
1348     // return result (property value) that occured most frequently
1349     return most_common_property_value_it->first;
1350 }
1351 
1352 ///////////////////////////////////////////////////////////
1353 // ComplexVariable                                       //
1354 ///////////////////////////////////////////////////////////
1355 template <>
Eval(const ScriptingContext & context) const1356 PlanetSize ComplexVariable<PlanetSize>::Eval(const ScriptingContext& context) const
1357 { return INVALID_PLANET_SIZE; }
1358 
1359 template <>
Eval(const ScriptingContext & context) const1360 PlanetType ComplexVariable<PlanetType>::Eval(const ScriptingContext& context) const
1361 { return INVALID_PLANET_TYPE; } // TODO: Species favourite planet type?
1362 
1363 template <>
Eval(const ScriptingContext & context) const1364 PlanetEnvironment ComplexVariable<PlanetEnvironment>::Eval(const ScriptingContext& context) const
1365 {
1366     const std::string& variable_name = m_property_name.back();
1367 
1368     if (variable_name == "PlanetEnvironmentForSpecies") {
1369         int planet_id = INVALID_OBJECT_ID;
1370         if (m_int_ref1)
1371             planet_id = m_int_ref1->Eval(context);
1372         const auto planet = context.ContextObjects().get<Planet>(planet_id);
1373         if (!planet)
1374             return INVALID_PLANET_ENVIRONMENT;
1375 
1376         std::string species_name;
1377         if (m_string_ref1)
1378             species_name = m_string_ref1->Eval(context);
1379         return planet->EnvironmentForSpecies(species_name);
1380     }
1381 
1382     return INVALID_PLANET_ENVIRONMENT;
1383 }
1384 
1385 template <>
Eval(const ScriptingContext & context) const1386 UniverseObjectType ComplexVariable<UniverseObjectType>::Eval(const ScriptingContext& context) const
1387 { return INVALID_UNIVERSE_OBJECT_TYPE; }
1388 
1389 template <>
Eval(const ScriptingContext & context) const1390 StarType ComplexVariable<StarType>::Eval(const ScriptingContext& context) const
1391 { return INVALID_STAR_TYPE; }
1392 
1393 template <>
Eval(const ScriptingContext & context) const1394 Visibility ComplexVariable<Visibility>::Eval(const ScriptingContext& context) const
1395 {
1396     const std::string& variable_name = m_property_name.back();
1397 
1398     if (variable_name == "EmpireObjectVisiblity") {
1399         int empire_id = ALL_EMPIRES;
1400         if (m_int_ref1) {
1401             empire_id = m_int_ref1->Eval(context);
1402             if (empire_id == ALL_EMPIRES)
1403                 return VIS_NO_VISIBILITY;
1404         }
1405 
1406         int object_id = INVALID_OBJECT_ID;
1407         if (m_int_ref2) {
1408             object_id = m_int_ref2->Eval(context);
1409             if (object_id == INVALID_OBJECT_ID)
1410                 return VIS_NO_VISIBILITY;
1411         }
1412 
1413         return GetUniverse().GetObjectVisibilityByEmpire(object_id, empire_id);
1414     }
1415 
1416     return INVALID_VISIBILITY;
1417 }
1418 
1419 template <>
Eval(const ScriptingContext & context) const1420 int ComplexVariable<int>::Eval(const ScriptingContext& context) const
1421 {
1422     const std::string& variable_name = m_property_name.back();
1423 
1424     std::function<const std::map<std::string, int>& (const Empire&)> empire_property_string_key{nullptr};
1425 
1426     if (variable_name == "BuildingTypesOwned")
1427         empire_property_string_key = &Empire::BuildingTypesOwned;
1428     if (variable_name == "BuildingTypesProduced")
1429         empire_property_string_key = &Empire::BuildingTypesProduced;
1430     if (variable_name == "BuildingTypesScrapped")
1431         empire_property_string_key = &Empire::BuildingTypesScrapped;
1432     if (variable_name == "SpeciesColoniesOwned")
1433         empire_property_string_key = &Empire::SpeciesColoniesOwned;
1434     if (variable_name == "SpeciesPlanetsBombed")
1435         empire_property_string_key = &Empire::SpeciesPlanetsBombed;
1436     if (variable_name == "SpeciesPlanetsDepoped")
1437         empire_property_string_key = &Empire::SpeciesPlanetsDepoped;
1438     if (variable_name == "SpeciesPlanetsInvaded")
1439         empire_property_string_key = &Empire::SpeciesPlanetsInvaded;
1440     if (variable_name == "SpeciesShipsDestroyed")
1441         empire_property_string_key = &Empire::SpeciesShipsDestroyed;
1442     if (variable_name == "SpeciesShipsLost")
1443         empire_property_string_key = &Empire::SpeciesShipsLost;
1444     if (variable_name == "SpeciesShipsOwned")
1445         empire_property_string_key = &Empire::SpeciesShipsOwned;
1446     if (variable_name == "SpeciesShipsProduced")
1447         empire_property_string_key = &Empire::SpeciesShipsProduced;
1448     if (variable_name == "SpeciesShipsScrapped")
1449         empire_property_string_key = &Empire::SpeciesShipsScrapped;
1450     if (variable_name == "ShipPartsOwned")
1451         empire_property_string_key = &Empire::ShipPartsOwned;
1452     if (variable_name == "TurnTechResearched")
1453         empire_property_string_key = &Empire::ResearchedTechs;
1454 
1455     // empire properties indexed by strings
1456     if (empire_property_string_key) {
1457         using namespace boost::adaptors;
1458 
1459         Empire* empire{nullptr};
1460         if (m_int_ref1) {
1461             int empire_id = m_int_ref1->Eval(context);
1462             if (empire_id == ALL_EMPIRES)
1463                 return 0;
1464             empire = GetEmpire(empire_id);
1465         }
1466 
1467         std::function<bool (const std::map<std::string, int>::value_type&)> key_filter{nullptr};
1468         key_filter = [](auto e){ return true; };
1469 
1470         if (m_string_ref1) {
1471             std::string key_string = m_string_ref1->Eval(context);
1472             if (key_string.empty())
1473                 return 0;
1474 
1475             if (empire && variable_name == "TurnTechResearched" && !empire->TechResearched(key_string))
1476                 // special case for techs: make unresearched-tech's research-turn a big number
1477                 return IMPOSSIBLY_LARGE_TURN;
1478 
1479             key_filter = [k = key_string](auto e){ return k == e.first; };
1480         }
1481         else if (variable_name == "ShipPartsOwned" && m_int_ref2) {
1482             int key_int = m_int_ref2->Eval(context);
1483             if (key_int <= INVALID_SHIP_PART_CLASS || key_int >= NUM_SHIP_PART_CLASSES)
1484                 return 0;
1485 
1486             auto key_filter = [part_class = ShipPartClass(key_int)](const std::map<ShipPartClass, int>::value_type& e){ return e.first == part_class; };
1487 
1488             if (empire)
1489                 return boost::accumulate(empire->ShipPartClassOwned() | filtered(key_filter) | map_values, 0);
1490 
1491             int sum = 0;
1492             for (const auto& empire_entry : Empires())
1493                 sum += boost::accumulate(empire_entry.second->ShipPartClassOwned() | filtered(key_filter) | map_values, 0);
1494             return sum;
1495         }
1496 
1497         if (empire)
1498             return boost::accumulate(empire_property_string_key(*empire) | filtered(key_filter) | map_values, 0);
1499 
1500         int sum = 0;
1501         for (const auto& empire_entry : Empires())
1502             sum += boost::accumulate(empire_property_string_key(*(empire_entry.second)) | filtered(key_filter) | map_values, 0);
1503         return sum;
1504     }
1505 
1506     std::function<const std::map<int, int>& (const Empire&)> empire_property_int_key{nullptr};
1507 
1508     if (variable_name == "EmpireShipsDestroyed")
1509         empire_property_int_key = &Empire::EmpireShipsDestroyed;
1510     if (variable_name == "ShipDesignsDestroyed")
1511         empire_property_int_key = &Empire::ShipDesignsDestroyed;
1512     if (variable_name == "ShipDesignsLost")
1513         empire_property_int_key = &Empire::ShipDesignsLost;
1514     if (variable_name == "ShipDesignsOwned")
1515         empire_property_int_key = &Empire::ShipDesignsOwned;
1516     if (variable_name == "ShipDesignsInProduction")
1517         empire_property_int_key = &Empire::ShipDesignsInProduction;
1518     if (variable_name == "ShipDesignsProduced")
1519         empire_property_int_key = &Empire::ShipDesignsProduced;
1520     if (variable_name == "ShipDesignsScrapped")
1521         empire_property_int_key = &Empire::ShipDesignsScrapped;
1522 
1523     // empire properties indexed by integers
1524     if (empire_property_int_key) {
1525         using namespace boost::adaptors;
1526 
1527         Empire* empire{nullptr};
1528         if (m_int_ref1) {
1529             int empire_id = m_int_ref1->Eval(context);
1530             if (empire_id == ALL_EMPIRES)
1531                 return 0;
1532             empire = GetEmpire(empire_id);
1533         }
1534 
1535         std::function<bool (const std::map<int, int>::value_type&)> key_filter{nullptr};
1536         key_filter = [](auto e){ return true; };
1537 
1538         // if a key integer specified, get just that entry (for single empire or sum of all empires)
1539         if (m_int_ref2)
1540             key_filter = [k = m_int_ref2->Eval(context)](auto e){ return k == e.first; };
1541 
1542         // although indexed by integers, some of these may be specified by a
1543         // string that needs to be looked up. if a key string specified, get
1544         // just that entry (for single empire or sum of all empires)
1545         if (m_string_ref1) {
1546             std::string key_string = m_string_ref1->Eval(context);
1547             if (key_string.empty())
1548                 return 0;
1549             int key_int = -1;
1550             if (boost::istarts_with(variable_name, "ShipDesign")) {
1551                 // look up ship design id corresponding to specified predefined ship design name
1552                 const ShipDesign* design = GetPredefinedShipDesign(key_string);
1553                 if (design)
1554                     key_int = design->ID();
1555             }
1556             key_filter = [k = key_int](auto e){ return k == e.first; };
1557         }
1558 
1559         if (empire)
1560             return boost::accumulate(empire_property_int_key(*empire) | filtered(key_filter) | map_values, 0);
1561 
1562         int sum = 0;
1563         for (const auto& empire_entry : Empires())
1564             sum += boost::accumulate(empire_property_int_key(*(empire_entry.second)) | filtered(key_filter) | map_values, 0);
1565         return sum;
1566     }
1567 
1568     // unindexed empire proprties
1569     if (variable_name == "OutpostsOwned") {
1570         Empire* empire{nullptr};
1571         if (m_int_ref1) {
1572             int empire_id = m_int_ref1->Eval(context);
1573             if (empire_id == ALL_EMPIRES)
1574                 return 0;
1575             empire = GetEmpire(empire_id);
1576             if (!empire)
1577                 return 0;
1578         }
1579 
1580         std::function<int (const Empire*)> empire_property{nullptr};
1581         empire_property = &Empire::OutpostsOwned;
1582 
1583         using namespace boost::adaptors;
1584 
1585         if (!empire)
1586             return boost::accumulate(Empires() | map_values | transformed(empire_property), 0);
1587 
1588         return empire_property(empire);
1589     }
1590 
1591     // non-empire properties
1592     if (variable_name == "GameRule") {
1593         if (!m_string_ref1)
1594             return 0;
1595         std::string rule_name = m_string_ref1->Eval();
1596         if (rule_name.empty())
1597             return 0;
1598         if (!GetGameRules().RuleExists(rule_name))
1599             return 0;
1600         try {
1601             // can cast boolean, int, or double-valued rules to int
1602             switch (GetGameRules().GetType(rule_name)) {
1603                 case GameRules::Type::TOGGLE: {
1604                 return GetGameRules().Get<bool>(rule_name);
1605                 break;
1606             }
1607             case GameRules::Type::INT: {
1608                 return GetGameRules().Get<int>(rule_name);
1609                 break;
1610             }
1611             case GameRules::Type::DOUBLE: {
1612                 return static_cast<int>(GetGameRules().Get<double>(rule_name));
1613                 break;
1614             }
1615             default:
1616                 break;
1617             }
1618         } catch (...) {
1619         }
1620         return 0;
1621     }
1622     else if (variable_name == "PartsInShipDesign") {
1623         int design_id = INVALID_DESIGN_ID;
1624         if (m_int_ref1) {
1625             design_id = m_int_ref1->Eval(context);
1626             if (design_id == INVALID_DESIGN_ID)
1627                 return 0;
1628         } else {
1629             return 0;
1630         }
1631 
1632         std::string ship_part_name;
1633         if (m_string_ref1) {
1634             ship_part_name = m_string_ref1->Eval(context);
1635         }
1636 
1637         const ShipDesign* design = GetShipDesign(design_id);
1638         if (!design)
1639             return 0;
1640 
1641         if (ship_part_name.empty())
1642             return design->PartCount();
1643 
1644         int count = 0;
1645         for (const std::string& part : design->Parts()) {
1646             if (ship_part_name == part)
1647                 count++;
1648         }
1649         return count;
1650     }
1651     else if (variable_name == "PartOfClassInShipDesign") {
1652         int design_id = INVALID_DESIGN_ID;
1653         if (m_int_ref1) {
1654             design_id = m_int_ref1->Eval(context);
1655             if (design_id == INVALID_DESIGN_ID)
1656                 return 0;
1657         } else {
1658             return 0;
1659         }
1660 
1661         const ShipDesign* design = GetShipDesign(design_id);
1662         if (!design)
1663             return 0;
1664 
1665         std::string part_class_name;
1666         if (m_string_ref1) {
1667             part_class_name = m_string_ref1->Eval(context);
1668         } else {
1669             return 0;
1670         }
1671         ShipPartClass part_class = INVALID_SHIP_PART_CLASS;
1672         try {
1673             part_class = boost::lexical_cast<ShipPartClass>(part_class_name);
1674         } catch (...) {
1675             return 0;
1676         }
1677 
1678         int count = 0;
1679         for (const std::string& part_name : design->Parts()) {
1680             if (part_name.empty())
1681                 continue;
1682             const ShipPart* part = GetShipPart(part_name);
1683             if (!part)
1684                 continue;
1685             if (part->Class() == part_class)
1686                 count++;
1687         }
1688         return count;
1689     }
1690     else if (variable_name == "JumpsBetween") {
1691         int object1_id = INVALID_OBJECT_ID;
1692         if (m_int_ref1)
1693             object1_id = m_int_ref1->Eval(context);
1694 
1695         int object2_id = INVALID_OBJECT_ID;
1696         if (m_int_ref2)
1697             object2_id = m_int_ref2->Eval(context);
1698 
1699         int retval = GetPathfinder()->JumpDistanceBetweenObjects(object1_id, object2_id);
1700         if (retval == INT_MAX)
1701             return -1;
1702         return retval;
1703     }
1704     else if (variable_name == "JumpsBetweenByEmpireSupplyConnections") {
1705         int object1_id = INVALID_OBJECT_ID;
1706         if (m_int_ref1)
1707             object1_id = m_int_ref1->Eval(context);
1708 
1709         int object2_id = INVALID_OBJECT_ID;
1710         if (m_int_ref2)
1711             object2_id = m_int_ref2->Eval(context);
1712 
1713         // TODO: implement supply-connect-restriction path length determination...
1714         // in the meantime, leave empire_id commented out to avoid unused var warning
1715         //int empire_id = ALL_EMPIRES;
1716         //if (m_int_ref3)
1717         //    empire_id = m_int_ref3->Eval(context);
1718 
1719         int retval = GetPathfinder()->JumpDistanceBetweenObjects(object1_id, object2_id/*, empire_id*/);
1720         if (retval == INT_MAX)
1721             return -1;
1722         return retval;
1723     }
1724     else if (variable_name == "SlotsInHull") {
1725         const ShipHull* ship_hull = nullptr;
1726         if (m_string_ref1) {
1727             std::string hull_name = m_string_ref1->Eval(context);
1728             ship_hull = GetShipHull(hull_name);
1729             if (!ship_hull)
1730                 return 0;
1731         } else {
1732             return 0;
1733         }
1734         return ship_hull->Slots().size();
1735     }
1736     else if (variable_name == "SlotsInShipDesign") {
1737         int design_id = INVALID_DESIGN_ID;
1738         if (m_int_ref1) {
1739             design_id = m_int_ref1->Eval(context);
1740             if (design_id == INVALID_DESIGN_ID)
1741                 return 0;
1742         } else {
1743             return 0;
1744         }
1745 
1746         const ShipDesign* design = GetShipDesign(design_id);
1747         if (!design)
1748             return 0;
1749 
1750         const ShipHull* ship_hull = GetShipHull(design->Hull());
1751         if (!ship_hull)
1752             return 0;
1753         return ship_hull->Slots().size();
1754     }
1755     else if (variable_name == "SpecialAddedOnTurn") {
1756         int object_id = INVALID_OBJECT_ID;
1757         if (m_int_ref1)
1758             object_id = m_int_ref1->Eval(context);
1759         if (object_id == INVALID_OBJECT_ID)
1760             return 0;
1761         auto object = context.ContextObjects().get(object_id);
1762         if (!object)
1763             return 0;
1764 
1765         std::string special_name;
1766         if (m_string_ref1)
1767             special_name = m_string_ref1->Eval(context);
1768         if (special_name.empty())
1769             return 0;
1770 
1771         return object->SpecialAddedOnTurn(special_name);
1772     }
1773 
1774     return 0;
1775 }
1776 
1777 template <>
Eval(const ScriptingContext & context) const1778 double ComplexVariable<double>::Eval(const ScriptingContext& context) const
1779 {
1780     const std::string& variable_name = m_property_name.back();
1781 
1782     std::function<float (const ShipHull&)> hull_property{nullptr};
1783 
1784     if (variable_name == "HullFuel")
1785         hull_property = &ShipHull::Fuel;
1786     else if (variable_name == "HullStealth")
1787         hull_property = &ShipHull::Stealth;
1788     else if (variable_name == "HullStructure")
1789         hull_property = &ShipHull::Structure;
1790     else if (variable_name == "HullSpeed")
1791         hull_property = &ShipHull::Speed;
1792 
1793     if (hull_property) {
1794         std::string ship_hull_name;
1795         if (m_string_ref1)
1796             ship_hull_name = m_string_ref1->Eval(context);
1797 
1798         const ShipHull* ship_hull = GetShipHull(ship_hull_name);
1799         if (!ship_hull)
1800             return 0.0f;
1801 
1802         return hull_property(*ship_hull);
1803     }
1804 
1805     // empire properties indexed by integers
1806     std::function<const std::map<int, float>& (const Empire&)> empire_property{nullptr};
1807 
1808     if (variable_name == "PropagatedSystemSupplyRange")
1809         empire_property = [](const Empire& empire){ return GetSupplyManager().PropagatedSupplyRanges(empire.EmpireID()); };
1810     if (variable_name == "SystemSupplyRange")
1811         empire_property = &Empire::SystemSupplyRanges;
1812     if (variable_name == "PropagatedSystemSupplyDistance")
1813         empire_property = [](const Empire& empire){ return GetSupplyManager().PropagatedSupplyDistances(empire.EmpireID()); };
1814 
1815     if (empire_property) {
1816         using namespace boost::adaptors;
1817 
1818         Empire* empire{nullptr};
1819 
1820         if (m_int_ref1) {
1821             int empire_id = m_int_ref1->Eval(context);
1822             if (empire_id == ALL_EMPIRES)
1823                 return 0.0;
1824             empire = GetEmpire(empire_id);
1825         }
1826 
1827         std::function<bool (const std::map<int, float>::value_type&)> key_filter;
1828         key_filter = [](auto k){ return true; };
1829 
1830         if (m_int_ref2)
1831             key_filter = [k = m_int_ref2->Eval(context)](auto e){ return k == e.first; };
1832 
1833         if (empire)
1834             return boost::accumulate(empire_property(*empire) | filtered(key_filter) | map_values, 0.0f);
1835 
1836         float sum = 0.0f;
1837         for (const auto& empire_entry : Empires())
1838             sum += boost::accumulate(empire_property(*(empire_entry.second)) | filtered(key_filter) | map_values, 0.0f);
1839         return sum;
1840     }
1841 
1842     // non-empire properties
1843     if (variable_name == "GameRule") {
1844         if (!m_string_ref1)
1845             return 0.0;
1846         std::string rule_name = m_string_ref1->Eval();
1847         if (rule_name.empty())
1848             return 0.0;
1849         if (!GetGameRules().RuleExists(rule_name))
1850             return 0.0;
1851         try {
1852             // can cast boolean, int, or double-valued rules to double
1853             switch (GetGameRules().GetType(rule_name)) {
1854             case GameRules::Type::TOGGLE: {
1855                 return GetGameRules().Get<bool>(rule_name);
1856                 break;
1857             }
1858             case GameRules::Type::INT: {
1859                 return GetGameRules().Get<int>(rule_name);
1860                 break;
1861             }
1862             case GameRules::Type::DOUBLE: {
1863                 return GetGameRules().Get<double>(rule_name);
1864                 break;
1865             }
1866             default:
1867                 break;
1868             }
1869         } catch (...) {
1870         }
1871         return 0.0;
1872     }
1873     else if (variable_name == "PartCapacity") {
1874         std::string ship_part_name;
1875         if (m_string_ref1)
1876             ship_part_name = m_string_ref1->Eval(context);
1877 
1878         const ShipPart* ship_part = GetShipPart(ship_part_name);
1879         if (!ship_part)
1880             return 0.0;
1881 
1882         return ship_part->Capacity();
1883 
1884     }
1885     else if (variable_name == "PartSecondaryStat") {
1886         std::string ship_part_name;
1887         if (m_string_ref1)
1888             ship_part_name = m_string_ref1->Eval(context);
1889 
1890         const ShipPart* ship_part = GetShipPart(ship_part_name);
1891         if (!ship_part)
1892             return 0.0;
1893 
1894         return ship_part->SecondaryStat();
1895 
1896     }
1897     else if (variable_name == "ShipDesignCost") {
1898         int design_id = INVALID_DESIGN_ID;
1899         if (m_int_ref1)
1900             design_id = m_int_ref1->Eval(context);
1901 
1902         const ShipDesign* design = GetShipDesign(design_id);
1903         if (!design)
1904             return 0.0;
1905 
1906         int empire_id = ALL_EMPIRES;
1907         if (m_int_ref2)
1908             empire_id = m_int_ref2->Eval(context);
1909 
1910         int location_id = INVALID_OBJECT_ID;
1911         if (m_int_ref3)
1912             location_id = m_int_ref3->Eval(context);
1913 
1914         return design->ProductionCost(empire_id, location_id);
1915 
1916     }
1917     else if (variable_name == "EmpireMeterValue") {
1918         int empire_id = ALL_EMPIRES;
1919         if (m_int_ref1)
1920             empire_id = m_int_ref1->Eval(context);
1921         Empire* empire = GetEmpire(empire_id);
1922         if (!empire)
1923             return 0.0;
1924 
1925         std::string empire_meter_name;
1926         if (m_string_ref1)
1927             empire_meter_name = m_string_ref1->Eval(context);
1928         Meter* meter = empire->GetMeter(empire_meter_name);
1929         if (!meter)
1930             return 0.0;
1931         return meter->Current();
1932     }
1933     else if (variable_name == "DirectDistanceBetween") {
1934         int object1_id = INVALID_OBJECT_ID;
1935         if (m_int_ref1)
1936             object1_id = m_int_ref1->Eval(context);
1937         auto obj1 = context.ContextObjects().get(object1_id);
1938         if (!obj1)
1939             return 0.0;
1940 
1941         int object2_id = INVALID_OBJECT_ID;
1942         if (m_int_ref2)
1943             object2_id = m_int_ref2->Eval(context);
1944         auto obj2 = context.ContextObjects().get(object2_id);
1945         if (!obj2)
1946             return 0.0;
1947 
1948         double dx = obj2->X() - obj1->X();
1949         double dy = obj2->Y() - obj1->Y();
1950         return static_cast<float>(std::sqrt(dx*dx + dy*dy));
1951 
1952     }
1953     else if (variable_name == "ShortestPath") {
1954         int object1_id = INVALID_OBJECT_ID;
1955         if (m_int_ref1)
1956             object1_id = m_int_ref1->Eval(context);
1957 
1958         int object2_id = INVALID_OBJECT_ID;
1959         if (m_int_ref2)
1960             object2_id = m_int_ref2->Eval(context);
1961 
1962         return GetPathfinder()->ShortestPathDistance(object1_id, object2_id);
1963 
1964     }
1965     else if (variable_name == "SpeciesEmpireOpinion") {
1966         int empire_id = ALL_EMPIRES;
1967         if (m_int_ref1)
1968             empire_id = m_int_ref1->Eval(context);
1969 
1970         std::string species_name;
1971         if (m_string_ref1)
1972             species_name = m_string_ref1->Eval(context);
1973 
1974         return GetSpeciesManager().SpeciesEmpireOpinion(species_name, empire_id);
1975 
1976     }
1977     else if (variable_name == "SpeciesSpeciesOpinion") {
1978         std::string opinionated_species_name;
1979         if (m_string_ref1)
1980             opinionated_species_name = m_string_ref1->Eval(context);
1981 
1982         std::string rated_species_name;
1983         if (m_string_ref2)
1984             rated_species_name = m_string_ref2->Eval(context);
1985 
1986         return GetSpeciesManager().SpeciesSpeciesOpinion(opinionated_species_name, rated_species_name);
1987     }
1988     else if (variable_name == "SpecialCapacity") {
1989         int object_id = INVALID_OBJECT_ID;
1990         if (m_int_ref1)
1991             object_id = m_int_ref1->Eval(context);
1992         auto object = context.ContextObjects().get(object_id);
1993         if (!object)
1994             return 0.0;
1995 
1996         std::string special_name;
1997         if (m_string_ref1)
1998             special_name = m_string_ref1->Eval(context);
1999         if (special_name.empty())
2000             return 0.0;
2001 
2002         return object->SpecialCapacity(special_name);
2003     }
2004     else if (variable_name == "ShipPartMeter") {
2005         int object_id = INVALID_OBJECT_ID;
2006         if (m_int_ref1)
2007             object_id = m_int_ref1->Eval(context);
2008         auto object = context.ContextObjects().get(object_id);
2009         if (!object)
2010             return 0.0;
2011         auto ship = std::dynamic_pointer_cast<const Ship>(object);
2012         if (!ship)
2013             return 0.0;
2014 
2015         std::string part_name;
2016         if (m_string_ref1)
2017             part_name = m_string_ref1->Eval(context);
2018         if (part_name.empty())
2019             return 0.0;
2020 
2021         std::string meter_name;
2022         if (m_string_ref2)
2023             meter_name = m_string_ref2->Eval(context);
2024         if (meter_name.empty())
2025             return 0.0;
2026 
2027         MeterType meter_type = NameToMeter(meter_name);
2028         if (meter_type != INVALID_METER_TYPE) {
2029             if (m_return_immediate_value)
2030                 return ship->CurrentPartMeterValue(meter_type, part_name);
2031             else
2032                 return ship->InitialPartMeterValue(meter_type, part_name);
2033         }
2034     }
2035 
2036     return 0.0;
2037 }
2038 
2039 namespace {
TechsResearchedByEmpire(int empire_id)2040     std::vector<std::string> TechsResearchedByEmpire(int empire_id) {
2041         std::vector<std::string> retval;
2042         const Empire* empire = GetEmpire(empire_id);
2043         if (!empire)
2044             return retval;
2045         for (const auto& tech : GetTechManager()) {
2046             if (empire->TechResearched(tech->Name()))
2047                 retval.push_back(tech->Name());
2048         }
2049         return retval;
2050     }
2051 
TechsResearchableByEmpire(int empire_id)2052     std::vector<std::string> TechsResearchableByEmpire(int empire_id) {
2053         std::vector<std::string> retval;
2054         const Empire* empire = GetEmpire(empire_id);
2055         if (!empire)
2056             return retval;
2057         for (const auto& tech : GetTechManager()) {
2058             if (empire->ResearchableTech(tech->Name()))
2059                 retval.push_back(tech->Name());
2060         }
2061         return retval;
2062     }
2063 
TransferrableTechs(int sender_empire_id,int receipient_empire_id)2064     std::vector<std::string> TransferrableTechs(int sender_empire_id, int receipient_empire_id) {
2065         std::vector<std::string> sender_researched_techs = TechsResearchedByEmpire(sender_empire_id);
2066         std::vector<std::string> recepient_researchable = TechsResearchableByEmpire(receipient_empire_id);
2067 
2068         std::vector<std::string> retval;
2069 
2070         if (sender_researched_techs.empty() || recepient_researchable.empty())
2071             return retval;
2072 
2073         // find intersection of two lists
2074         std::sort(sender_researched_techs.begin(), sender_researched_techs.end());
2075         std::sort(recepient_researchable.begin(), recepient_researchable.end());
2076         std::set_intersection(sender_researched_techs.begin(), sender_researched_techs.end(),
2077                               recepient_researchable.begin(), recepient_researchable.end(),
2078                               std::back_inserter(retval));
2079 
2080         // find techs common to both lists
2081         return retval;
2082     }
2083 }
2084 
2085 template <>
Eval(const ScriptingContext & context) const2086 std::string ComplexVariable<std::string>::Eval(const ScriptingContext& context) const
2087 {
2088     const std::string& variable_name = m_property_name.back();
2089 
2090     std::function<std::string (const Empire&)> empire_property{nullptr};
2091     auto null_property = [](const Empire&) -> std::string { return ""; };
2092 
2093     // unindexed empire properties
2094     if (variable_name == "LowestCostEnqueuedTech")
2095         empire_property = &Empire::LeastExpensiveEnqueuedTech;
2096     else if (variable_name == "HighestCostEnqueuedTech")
2097         empire_property = &Empire::MostExpensiveEnqueuedTech;
2098     else if (variable_name == "TopPriorityEnqueuedTech")
2099         empire_property = &Empire::TopPriorityEnqueuedTech;
2100     else if (variable_name == "MostSpentEnqueuedTech")
2101         empire_property = &Empire::MostRPSpentEnqueuedTech;
2102     else if (variable_name == "LowestCostResearchableTech")
2103         empire_property = &Empire::LeastExpensiveResearchableTech;
2104     else if (variable_name == "HighestCostResearchableTech")
2105         empire_property = &Empire::MostExpensiveResearchableTech;
2106     else if (variable_name == "TopPriorityResearchableTech")
2107         empire_property = &Empire::TopPriorityResearchableTech;
2108     else if (variable_name == "MostSpentResearchableTech")
2109         empire_property = &Empire::MostExpensiveResearchableTech;
2110     else if (variable_name == "MostSpentTransferrableTech")
2111         empire_property = null_property;
2112     else if (variable_name == "RandomTransferrableTech")
2113         empire_property = null_property;
2114     else if (variable_name == "MostPopulousSpecies")
2115         empire_property = null_property;
2116     else if (variable_name == "MostHappySpecies")
2117         empire_property = null_property;
2118     else if (variable_name == "LeastHappySpecies")
2119         empire_property = null_property;
2120     else if (variable_name == "RandomColonizableSpecies")
2121         empire_property = null_property;
2122     else if (variable_name == "RandomControlledSpecies")
2123         empire_property = null_property;
2124 
2125     if (empire_property) {
2126         int empire_id = ALL_EMPIRES;
2127         if (m_int_ref1) {
2128             empire_id = m_int_ref1->Eval(context);
2129             if (empire_id == ALL_EMPIRES)
2130                 return "";
2131         }
2132         const Empire* empire = GetEmpire(empire_id);
2133         if (!empire)
2134             return "";
2135 
2136         return empire_property(*empire);
2137     }
2138 
2139     if (variable_name == "RandomEnqueuedTech") {
2140         int empire_id = ALL_EMPIRES;
2141         if (m_int_ref1) {
2142             empire_id = m_int_ref1->Eval(context);
2143             if (empire_id == ALL_EMPIRES)
2144                 return "";
2145         }
2146         const Empire* empire = GetEmpire(empire_id);
2147         if (!empire)
2148             return "";
2149         // get all techs on queue, randomly pick one
2150         const ResearchQueue& queue = empire->GetResearchQueue();
2151         std::vector<std::string> all_enqueued_techs = queue.AllEnqueuedProjects();
2152         if (all_enqueued_techs.empty())
2153             return "";
2154         std::size_t idx = RandSmallInt(0, static_cast<int>(all_enqueued_techs.size()) - 1);
2155         return *std::next(all_enqueued_techs.begin(), idx);
2156 
2157     } else if (variable_name == "RandomResearchableTech") {
2158         int empire_id = ALL_EMPIRES;
2159         if (m_int_ref1) {
2160             empire_id = m_int_ref1->Eval(context);
2161             if (empire_id == ALL_EMPIRES)
2162                 return "";
2163         }
2164         const Empire* empire = GetEmpire(empire_id);
2165         if (!empire)
2166             return "";
2167 
2168         std::vector<std::string> researchable_techs = TechsResearchableByEmpire(empire_id);
2169         if (researchable_techs.empty())
2170             return "";
2171         std::size_t idx = RandSmallInt(0, static_cast<int>(researchable_techs.size()) - 1);
2172         return *std::next(researchable_techs.begin(), idx);
2173     } else if (variable_name == "RandomCompleteTech") {
2174         int empire_id = ALL_EMPIRES;
2175         if (m_int_ref1) {
2176             empire_id = m_int_ref1->Eval(context);
2177             if (empire_id == ALL_EMPIRES)
2178                 return "";
2179         }
2180         const Empire* empire = GetEmpire(empire_id);
2181         if (!empire)
2182             return "";
2183 
2184         std::vector<std::string> complete_techs = TechsResearchedByEmpire(empire_id);
2185         if (complete_techs.empty())
2186             return "";
2187         std::size_t idx = RandSmallInt(0, static_cast<int>(complete_techs.size()) - 1);
2188         return *std::next(complete_techs.begin(), idx);
2189     } else if (variable_name == "LowestCostTransferrableTech") {
2190         int empire1_id = ALL_EMPIRES;
2191         if (m_int_ref1) {
2192             empire1_id = m_int_ref1->Eval(context);
2193             if (empire1_id == ALL_EMPIRES)
2194                 return "";
2195         }
2196 
2197         int empire2_id = ALL_EMPIRES;
2198         if (m_int_ref2) {
2199             empire2_id = m_int_ref2->Eval(context);
2200             if (empire2_id == ALL_EMPIRES)
2201                 return "";
2202         }
2203 
2204         std::vector<std::string> sendable_techs = TransferrableTechs(empire1_id, empire2_id);
2205         if (sendable_techs.empty())
2206             return "";
2207         std::size_t idx = RandSmallInt(0, static_cast<int>(sendable_techs.size()) - 1);
2208         return *std::next(sendable_techs.begin(), idx);
2209 
2210     } else if (variable_name == "HighestCostTransferrableTech") {
2211         int empire1_id = ALL_EMPIRES;
2212         if (m_int_ref1) {
2213             empire1_id = m_int_ref1->Eval(context);
2214             if (empire1_id == ALL_EMPIRES)
2215                 return "";
2216         }
2217 
2218         int empire2_id = ALL_EMPIRES;
2219         if (m_int_ref2) {
2220             empire2_id = m_int_ref2->Eval(context);
2221             if (empire2_id == ALL_EMPIRES)
2222                 return "";
2223         }
2224 
2225         std::vector<std::string> sendable_techs = TransferrableTechs(empire1_id, empire2_id);
2226         if (sendable_techs.empty())
2227             return "";
2228 
2229         std::string retval;
2230         float highest_cost = 0.0f;
2231         for (const std::string& tech_name : sendable_techs) {
2232             const Tech* tech = GetTech(tech_name);
2233             if (!tech)
2234                 continue;
2235             float rc = tech->ResearchCost(empire2_id);
2236             if (rc > highest_cost) {
2237                 highest_cost = rc;
2238                 retval = tech_name;
2239             }
2240         }
2241         return retval;
2242 
2243     } else if (variable_name == "TopPriorityTransferrableTech") {
2244         int empire1_id = ALL_EMPIRES;
2245         if (m_int_ref1) {
2246             empire1_id = m_int_ref1->Eval(context);
2247             if (empire1_id == ALL_EMPIRES)
2248                 return "";
2249         }
2250 
2251         int empire2_id = ALL_EMPIRES;
2252         if (m_int_ref2) {
2253             empire2_id = m_int_ref2->Eval(context);
2254             if (empire2_id == ALL_EMPIRES)
2255                 return "";
2256         }
2257         const Empire* empire2 = GetEmpire(empire2_id);
2258         if (!empire2)
2259             return "";
2260 
2261         std::vector<std::string> sendable_techs = TransferrableTechs(empire1_id, empire2_id);
2262         if (sendable_techs.empty())
2263             return "";
2264 
2265         std::string retval = *sendable_techs.begin();   // pick first tech by default, hopefully to be replaced below
2266         int position_of_top_found_tech = INT_MAX;
2267 
2268         // search queue to find which transferrable tech is at the top of the list
2269         const ResearchQueue& queue = empire2->GetResearchQueue();
2270         for (const std::string& tech : sendable_techs) {
2271             auto queue_it = queue.find(tech);
2272             if (queue_it == queue.end())
2273                 continue;
2274             int queue_pos = std::distance(queue.begin(), queue_it);
2275             if (queue_pos < position_of_top_found_tech) {
2276                 retval = tech;
2277                 position_of_top_found_tech = queue_pos;
2278             }
2279         }
2280         return retval;
2281     }
2282 
2283     // non-empire properties
2284     if (variable_name == "GameRule") {
2285         if (!m_string_ref1)
2286             return "";
2287         std::string rule_name = m_string_ref1->Eval();
2288         if (rule_name.empty())
2289             return "";
2290         if (!GetGameRules().RuleExists(rule_name))
2291             return "";
2292         try {
2293             // can cast boolean, int, double, or string-valued rules to strings
2294             switch (GetGameRules().GetType(rule_name)) {
2295             case GameRules::Type::TOGGLE: {
2296                 return std::to_string(GetGameRules().Get<bool>(rule_name));
2297                 break;
2298             }
2299             case GameRules::Type::INT: {
2300                 return std::to_string(GetGameRules().Get<int>(rule_name));
2301                 break;
2302             }
2303             case GameRules::Type::DOUBLE: {
2304                 return DoubleToString(GetGameRules().Get<double>(rule_name), 3, false);
2305                 break;
2306             }
2307             case GameRules::Type::STRING: {
2308                 return GetGameRules().Get<std::string>(rule_name);
2309                 break;
2310             }
2311             default:
2312                 break;
2313             }
2314         } catch (...) {
2315         }
2316         return "";
2317     }
2318 
2319     return "";
2320 }
2321 
2322 #undef IF_CURRENT_VALUE
2323 
2324 template <>
Dump(unsigned short ntabs) const2325 std::string ComplexVariable<Visibility>::Dump(unsigned short ntabs) const
2326 {
2327     const std::string& variable_name = m_property_name.back();
2328     std::string retval = variable_name;
2329 
2330     if (variable_name == "EmpireObjectVisiblity") {
2331         if (m_int_ref1)
2332             retval += " empire = " + m_int_ref1->Dump(ntabs);
2333         if (m_int_ref2)
2334             retval += " object = " + m_int_ref2->Dump(ntabs);
2335     }
2336 
2337     return retval;
2338 }
2339 
2340 template <>
Dump(unsigned short ntabs) const2341 std::string ComplexVariable<double>::Dump(unsigned short ntabs) const
2342 {
2343     const std::string& variable_name = m_property_name.back();
2344     std::string retval = variable_name;
2345 
2346     // empire properties indexed by integers
2347     if (variable_name == "PropagatedSystemSupplyRange" ||
2348         variable_name == "SystemSupplyRange" ||
2349         variable_name == "PropagatedSystemSupplyDistance")
2350     {
2351         if (m_int_ref1)
2352             retval += " empire = " + m_int_ref1->Dump(ntabs);
2353         if (m_int_ref2)
2354             retval += " system = " + m_int_ref2->Dump(ntabs);
2355 
2356     }
2357     else if (variable_name == "GameRule" ||
2358              variable_name == "HullFuel" ||
2359              variable_name == "HullStealth" ||
2360              variable_name == "HullStructure" ||
2361              variable_name == "HullSpeed" ||
2362              variable_name == "PartCapacity" ||
2363              variable_name == "PartSecondaryStat")
2364     {
2365         if (m_string_ref1)
2366             retval += " name = " + m_string_ref1->Dump(ntabs);
2367 
2368     }
2369     else if (variable_name == "EmpireMeterValue") {
2370         if (m_int_ref1)
2371             retval += " empire = " + m_int_ref1->Dump(ntabs);
2372         if (m_string_ref1)
2373             retval += " meter = " + m_string_ref1->Dump(ntabs);
2374 
2375     }
2376     else if (variable_name == "ShipPartMeter") {
2377         // ShipPartMeter part = "SR_WEAPON_1_1" meter = Capacity object = Source.ID
2378         if (m_string_ref1)
2379             retval += " part = " + m_string_ref1->Dump(ntabs);
2380         if (m_string_ref2)
2381             retval += " meter = " + m_string_ref2->Dump(ntabs); // wrapped in quotes " but shouldn't be to be consistent with parser
2382         if (m_int_ref1)
2383             retval += " object = " + m_int_ref1->Dump(ntabs);
2384 
2385     }
2386     else if (variable_name == "DirectDistanceBetween" ||
2387              variable_name == "ShortestPath")
2388     {
2389         if (m_int_ref1)
2390             retval += " object = " + m_int_ref1->Dump(ntabs);
2391         if (m_int_ref2)
2392             retval += " object = " + m_int_ref2->Dump(ntabs);
2393 
2394     }
2395     else if (variable_name == "SpeciesEmpireOpinion") {
2396         if (m_int_ref1)
2397             retval += " empire = " + m_int_ref1->Dump(ntabs);
2398         if (m_string_ref1)
2399             retval += " species = " + m_string_ref1->Dump(ntabs);
2400 
2401     }
2402     else if (variable_name == "SpeciesSpeciesOpinion") {
2403         if (m_string_ref1)
2404             retval += " species = " + m_string_ref1->Dump(ntabs);
2405         if (m_string_ref2)
2406             retval += " species = " + m_string_ref2->Dump(ntabs);
2407 
2408     }
2409     else if (variable_name == "SpecialCapacity") {
2410         if (m_string_ref1)
2411             retval += " name = " + m_string_ref1->Dump(ntabs);
2412         if (m_int_ref1)
2413             retval += " object = " + m_int_ref1->Dump(ntabs);
2414 
2415     }
2416 
2417     return retval;
2418 }
2419 
2420 template <>
Dump(unsigned short ntabs) const2421 std::string ComplexVariable<int>::Dump(unsigned short ntabs) const
2422 {
2423     const std::string& variable_name = m_property_name.back();
2424     std::string retval = variable_name;
2425 
2426     return retval;
2427 }
2428 
2429 template <>
Dump(unsigned short ntabs) const2430 std::string ComplexVariable<std::string>::Dump(unsigned short ntabs) const
2431 {
2432     const std::string& variable_name = m_property_name.back();
2433     std::string retval = variable_name;
2434 
2435     return retval;
2436 }
2437 
2438 ///////////////////////////////////////////////////////////
2439 // StringCast                                            //
2440 ///////////////////////////////////////////////////////////
2441 template <>
Eval(const ScriptingContext & context) const2442 std::string StringCast<double>::Eval(const ScriptingContext& context) const
2443 {
2444     if (!m_value_ref)
2445         return "";
2446     double temp = m_value_ref->Eval(context);
2447 
2448     // special case for a few sub-value-refs to help with UI representation
2449     if (Variable<double>* int_var = dynamic_cast<Variable<double>*>(m_value_ref.get())) {
2450         if (int_var->PropertyName().back() == "X" || int_var->PropertyName().back() == "Y") {
2451             if (temp == UniverseObject::INVALID_POSITION)
2452                 return UserString("INVALID_POSITION");
2453 
2454             std::stringstream ss;
2455             ss << std::setprecision(6) << temp;
2456             return ss.str();
2457         }
2458     }
2459 
2460     return DoubleToString(temp, 3, false);
2461 }
2462 
2463 template <>
Eval(const ScriptingContext & context) const2464 std::string StringCast<int>::Eval(const ScriptingContext& context) const
2465 {
2466     if (!m_value_ref)
2467         return "";
2468     int temp = m_value_ref->Eval(context);
2469 
2470     // special case for a few sub-value-refs to help with UI representation
2471     if (Variable<int>* int_var = dynamic_cast<Variable<int>*>(m_value_ref.get())) {
2472         if (int_var->PropertyName().back() == "ETA") {
2473             if (temp == Fleet::ETA_UNKNOWN) {
2474                 return UserString("FW_FLEET_ETA_UNKNOWN");
2475             } else if (temp == Fleet::ETA_NEVER) {
2476                 return UserString("FW_FLEET_ETA_NEVER");
2477             } else if (temp == Fleet::ETA_OUT_OF_RANGE) {
2478                 return UserString("FW_FLEET_ETA_OUT_OF_RANGE");
2479             }
2480         }
2481     }
2482 
2483     return std::to_string(temp);
2484 }
2485 
2486 template <>
Eval(const ScriptingContext & context) const2487 std::string StringCast<std::vector<std::string>>::Eval(const ScriptingContext& context) const
2488 {
2489     if (!m_value_ref)
2490         return "";
2491     std::vector<std::string> temp = m_value_ref->Eval(context);
2492 
2493     // concatenate strings into one big string
2494     std::string retval;
2495     for (auto str : temp)
2496         retval += str + " ";
2497     return retval;
2498 }
2499 
2500 ///////////////////////////////////////////////////////////
2501 // UserStringLookup                                      //
2502 ///////////////////////////////////////////////////////////
2503 template <>
Eval(const ScriptingContext & context) const2504 std::string UserStringLookup<std::string>::Eval(const ScriptingContext& context) const {
2505     if (!m_value_ref)
2506         return "";
2507     std::string ref_val = m_value_ref->Eval(context);
2508     if (ref_val.empty() || !UserStringExists(ref_val))
2509         return "";
2510     return UserString(ref_val);
2511 }
2512 
2513 template <>
Eval(const ScriptingContext & context) const2514 std::string UserStringLookup<std::vector<std::string>>::Eval(const ScriptingContext& context) const {
2515     if (!m_value_ref)
2516         return "";
2517     std::vector<std::string> ref_vals = m_value_ref->Eval(context);
2518     if (ref_vals.empty())
2519         return "";
2520     std::string retval;
2521     for (auto val : ref_vals) {
2522         if (val.empty() || !UserStringExists(val))
2523             continue;
2524         retval += UserString(val) + " ";
2525     }
2526     return retval;
2527 }
2528 
2529 /////////////////////////////////////////////////////
2530 // NameLookup                                      //
2531 /////////////////////////////////////////////////////
NameLookup(std::unique_ptr<ValueRef<int>> && value_ref,LookupType lookup_type)2532 NameLookup::NameLookup(std::unique_ptr<ValueRef<int>>&& value_ref, LookupType lookup_type) :
2533     Variable<std::string>(NON_OBJECT_REFERENCE),
2534     m_value_ref(std::move(value_ref)),
2535     m_lookup_type(lookup_type)
2536 {}
2537 
operator ==(const ValueRef<std::string> & rhs) const2538 bool NameLookup::operator==(const ValueRef<std::string>& rhs) const {
2539     if (&rhs == this)
2540         return true;
2541     if (typeid(rhs) != typeid(*this))
2542         return false;
2543     const NameLookup& rhs_ =
2544         static_cast<const NameLookup&>(rhs);
2545 
2546     if (m_lookup_type == rhs_.m_lookup_type) {
2547         // check next member
2548     } else {
2549         return false;
2550     }
2551 
2552     if (m_value_ref == rhs_.m_value_ref) {
2553         // check next member
2554     } else if (!m_value_ref || !rhs_.m_value_ref) {
2555         return false;
2556     } else {
2557         if (*m_value_ref != *(rhs_.m_value_ref))
2558             return false;
2559     }
2560 
2561     return true;
2562 }
2563 
Eval(const ScriptingContext & context) const2564 std::string NameLookup::Eval(const ScriptingContext& context) const {
2565     if (!m_value_ref || m_lookup_type == INVALID_LOOKUP)
2566         return "";
2567 
2568     switch (m_lookup_type) {
2569     case OBJECT_NAME: {
2570         auto obj = context.ContextObjects().get(m_value_ref->Eval(context));
2571         return obj ? obj->Name() : "";
2572         break;
2573     }
2574     case EMPIRE_NAME: {
2575         const Empire* empire = GetEmpire(m_value_ref->Eval(context));
2576         return empire ? empire->Name() : "";
2577         break;
2578     }
2579     case SHIP_DESIGN_NAME: {
2580         const ShipDesign* design = GetShipDesign(m_value_ref->Eval(context));
2581         return design ? design->Name() : "";
2582         break;
2583     }
2584     default:
2585         return "";
2586     }
2587 }
2588 
RootCandidateInvariant() const2589 bool NameLookup::RootCandidateInvariant() const
2590 { return m_value_ref->RootCandidateInvariant(); }
2591 
LocalCandidateInvariant() const2592 bool NameLookup::LocalCandidateInvariant() const
2593 { return !m_value_ref || m_value_ref->LocalCandidateInvariant(); }
2594 
TargetInvariant() const2595 bool NameLookup::TargetInvariant() const
2596 { return !m_value_ref || m_value_ref->TargetInvariant(); }
2597 
SourceInvariant() const2598 bool NameLookup::SourceInvariant() const
2599 { return !m_value_ref || m_value_ref->SourceInvariant(); }
2600 
Description() const2601 std::string NameLookup::Description() const
2602 { return m_value_ref->Description(); }
2603 
Dump(unsigned short ntabs) const2604 std::string NameLookup::Dump(unsigned short ntabs) const
2605 { return m_value_ref->Dump(ntabs); }
2606 
SetTopLevelContent(const std::string & content_name)2607 void NameLookup::SetTopLevelContent(const std::string& content_name) {
2608     if (m_value_ref)
2609         m_value_ref->SetTopLevelContent(content_name);
2610 }
2611 
GetCheckSum() const2612 unsigned int NameLookup::GetCheckSum() const {
2613     unsigned int retval{0};
2614 
2615     CheckSums::CheckSumCombine(retval, "ValueRef::NameLookup");
2616     CheckSums::CheckSumCombine(retval, m_value_ref);
2617     CheckSums::CheckSumCombine(retval, m_lookup_type);
2618     std::cout << "GetCheckSum(NameLookup): " << typeid(*this).name() << " retval: " << retval << std::endl << std::endl;
2619     return retval;
2620 }
2621 
2622 ///////////////////////////////////////////////////////////
2623 // Operation                                             //
2624 ///////////////////////////////////////////////////////////
2625 template <>
EvalImpl(const ScriptingContext & context) const2626 std::string Operation<std::string>::EvalImpl(const ScriptingContext& context) const
2627 {
2628     if (m_op_type == PLUS) {
2629         return LHS()->Eval(context) + RHS()->Eval(context);
2630 
2631     } else if (m_op_type == TIMES) {
2632         // useful for writing a "Statistic If" expression with strings. Number-
2633         // valued types return 0 or 1 for nothing or something matching the sampling
2634         // condition. For strings, an empty string indicates no matches, and non-empty
2635         // string indicates matches, which is treated like a multiplicative identity
2636         // operation, so just returns the RHS of the expression.
2637         if (LHS()->Eval(context).empty())
2638             return "";
2639         return RHS()->Eval(context);
2640 
2641     } else if (m_op_type == MINIMUM || m_op_type == MAXIMUM) {
2642         // evaluate all operands, return sorted first/last
2643         std::set<std::string> vals;
2644         for (auto& vr : m_operands) {
2645             if (vr)
2646                 vals.insert(vr->Eval(context));
2647         }
2648         if (m_op_type == MINIMUM)
2649             return vals.empty() ? "" : *vals.begin();
2650         else
2651             return vals.empty() ? "" : *vals.rbegin();
2652 
2653     } else if (m_op_type == RANDOM_PICK) {
2654         // select one operand, evaluate it, return result
2655         if (m_operands.empty())
2656             return "";
2657         unsigned int idx = RandSmallInt(0, m_operands.size() - 1);
2658         auto& vr = *std::next(m_operands.begin(), idx);
2659         if (!vr)
2660             return "";
2661         return vr->Eval(context);
2662 
2663     } else if (m_op_type == SUBSTITUTION) {
2664         // insert string into other string in place of %1% or similar placeholder
2665         if (m_operands.empty())
2666             return "";
2667         auto& template_op = *(m_operands.begin());
2668         if (!template_op)
2669             return "";
2670         std::string template_str = template_op->Eval(context);
2671 
2672         boost::format formatter = FlexibleFormat(template_str);
2673 
2674         for (auto& op : m_operands) {
2675             if (!op) {
2676                 formatter % "";
2677                 continue;
2678             }
2679             formatter % op->Eval(context);
2680         }
2681         return formatter.str();
2682 
2683     } else if (m_op_type >= COMPARE_EQUAL && m_op_type <= COMPARE_NOT_EQUAL) {
2684         const std::string&& lhs_val = LHS()->Eval(context);
2685         const std::string&& rhs_val = RHS()->Eval(context);
2686         bool test_result = false;
2687         switch (m_op_type) {
2688             case COMPARE_EQUAL:                 test_result = lhs_val == rhs_val;   break;
2689             case COMPARE_GREATER_THAN:          test_result = lhs_val > rhs_val;    break;
2690             case COMPARE_GREATER_THAN_OR_EQUAL: test_result = lhs_val >= rhs_val;   break;
2691             case COMPARE_LESS_THAN:             test_result = lhs_val < rhs_val;    break;
2692             case COMPARE_LESS_THAN_OR_EQUAL:    test_result = lhs_val <= rhs_val;   break;
2693             case COMPARE_NOT_EQUAL:             test_result = lhs_val != rhs_val;   break;
2694             default:    break;  // ??? do nothing, default to false
2695         }
2696         if (m_operands.size() < 3) {
2697             return test_result ? "true" : "false";
2698         } else if (m_operands.size() < 4) {
2699             if (test_result)
2700                 return m_operands[2]->Eval(context);
2701             else
2702                 return "false";
2703         } else {
2704             if (test_result)
2705                 return m_operands[2]->Eval(context);
2706             else
2707                 return m_operands[3]->Eval(context);
2708         }
2709     }
2710 
2711     throw std::runtime_error("std::string ValueRef evaluated with an unknown or invalid OpType.");
2712     return "";
2713 }
2714 
2715 template <>
EvalImpl(const ScriptingContext & context) const2716 double Operation<double>::EvalImpl(const ScriptingContext& context) const
2717 {
2718     switch (m_op_type) {
2719         case PLUS:
2720             return LHS()->Eval(context) + RHS()->Eval(context); break;
2721 
2722         case MINUS:
2723             return LHS()->Eval(context) - RHS()->Eval(context); break;
2724 
2725         case TIMES: {
2726             double op1 = LHS()->Eval(context);
2727             if (op1 == 0.0)
2728                 return 0.0;
2729             return op1 * RHS()->Eval(context);
2730             break;
2731         }
2732 
2733         case DIVIDE: {
2734             double op2 = RHS()->Eval(context);
2735             if (op2 == 0.0)
2736                 return 0.0;
2737             return LHS()->Eval(context) / op2;
2738             break;
2739         }
2740 
2741         case NEGATE:
2742             return -(LHS()->Eval(context)); break;
2743 
2744         case EXPONENTIATE: {
2745             double op2 = RHS()->Eval(context);
2746             if (op2 == 0.0)
2747                 return 1.0;
2748             try {
2749                 double op1 = LHS()->Eval(context);
2750                 return std::pow(op1, op2);
2751             } catch (...) {
2752                 ErrorLogger() << "Error evaluating exponentiation ValueRef::Operation";
2753                 return 0.0;
2754             }
2755             break;
2756         }
2757 
2758         case ABS:
2759             return std::abs(LHS()->Eval(context)); break;
2760 
2761         case LOGARITHM: {
2762             double op1 = LHS()->Eval(context);
2763             if (op1 <= 0.0)
2764                 return 0.0;
2765             return std::log(op1);
2766             break;
2767         }
2768 
2769         case SINE:
2770             return std::sin(LHS()->Eval(context)); break;
2771 
2772         case COSINE:
2773             return std::cos(LHS()->Eval(context)); break;
2774 
2775         case MINIMUM:
2776         case MAXIMUM: {
2777             std::set<double> vals;
2778             for (auto& vr : m_operands) {
2779                 if (vr)
2780                     vals.insert(vr->Eval(context));
2781             }
2782             if (m_op_type == MINIMUM)
2783                 return vals.empty() ? 0.0 : *vals.begin();
2784             else
2785                 return vals.empty() ? 0.0 : *vals.rbegin();
2786             break;
2787         }
2788 
2789         case RANDOM_UNIFORM: {
2790             double op1 = LHS()->Eval(context);
2791             double op2 = RHS()->Eval(context);
2792             double min_val = std::min(op1, op2);
2793             double max_val = std::max(op1, op2);
2794             return RandDouble(min_val, max_val);
2795             break;
2796         }
2797 
2798         case RANDOM_PICK: {
2799             // select one operand, evaluate it, return result
2800             if (m_operands.empty())
2801                 return 0.0;
2802             unsigned int idx = RandSmallInt(0, m_operands.size() - 1);
2803             auto& vr = *std::next(m_operands.begin(), idx);
2804             if (!vr)
2805                 return 0.0;
2806             return vr->Eval(context);
2807             break;
2808         }
2809 
2810         case COMPARE_EQUAL:
2811         case COMPARE_GREATER_THAN:
2812         case COMPARE_GREATER_THAN_OR_EQUAL:
2813         case COMPARE_LESS_THAN:
2814         case COMPARE_LESS_THAN_OR_EQUAL:
2815         case COMPARE_NOT_EQUAL: {
2816             const double&& lhs_val = LHS()->Eval(context);
2817             const double&& rhs_val = RHS()->Eval(context);
2818             bool test_result = false;
2819             switch (m_op_type) {
2820                 case COMPARE_EQUAL:                 test_result = lhs_val == rhs_val;   break;
2821                 case COMPARE_GREATER_THAN:          test_result = lhs_val > rhs_val;    break;
2822                 case COMPARE_GREATER_THAN_OR_EQUAL: test_result = lhs_val >= rhs_val;   break;
2823                 case COMPARE_LESS_THAN:             test_result = lhs_val < rhs_val;    break;
2824                 case COMPARE_LESS_THAN_OR_EQUAL:    test_result = lhs_val <= rhs_val;   break;
2825                 case COMPARE_NOT_EQUAL:             test_result = lhs_val != rhs_val;   break;
2826                 default:    break;  // ??? do nothing, default to false
2827             }
2828             if (m_operands.size() < 3) {
2829                 return static_cast<double>(test_result);
2830             } else if (m_operands.size() < 4) {
2831                 if (test_result)
2832                     return m_operands[2]->Eval(context);
2833                 else
2834                     return 0.0;
2835             } else {
2836                 if (test_result)
2837                     return m_operands[2]->Eval(context);
2838                 else
2839                     return m_operands[3]->Eval(context);
2840             }
2841         }
2842 
2843         case ROUND_NEAREST:
2844             return std::round(LHS()->Eval(context)); break;
2845         case ROUND_UP:
2846             return std::ceil(LHS()->Eval(context)); break;
2847         case ROUND_DOWN:
2848             return std::floor(LHS()->Eval(context)); break;
2849 
2850         default:
2851             break;
2852     }
2853 
2854     throw std::runtime_error("double ValueRef evaluated with an unknown or invalid OpType.");
2855     return 0.0;
2856 }
2857 
2858 template <>
EvalImpl(const ScriptingContext & context) const2859 int Operation<int>::EvalImpl(const ScriptingContext& context) const
2860 {
2861     switch (m_op_type) {
2862         case PLUS:
2863             return LHS()->Eval(context) + RHS()->Eval(context);     break;
2864 
2865         case MINUS:
2866             return LHS()->Eval(context) - RHS()->Eval(context);     break;
2867 
2868         case TIMES: {
2869             double op1 = LHS()->Eval(context);
2870             if (op1 == 0)
2871                 return 0;
2872             return op1 * RHS()->Eval(context);
2873             break;
2874         }
2875 
2876         case DIVIDE: {
2877             int op2 = RHS()->Eval(context);
2878             if (op2 == 0)
2879                 return 0;
2880             return LHS()->Eval(context) / op2;
2881             break;
2882         }
2883 
2884         case NEGATE:
2885             return -LHS()->Eval(context); break;
2886 
2887         case EXPONENTIATE: {
2888             double op2 = RHS()->Eval(context);
2889             if (op2 == 0)
2890                 return 1;
2891             try {
2892                 double op1 = LHS()->Eval(context);
2893                 return static_cast<int>(std::pow(op1, op2));
2894             } catch (...) {
2895                 ErrorLogger() << "Error evaluating exponentiation ValueRef::Operation";
2896                 return 0;
2897             }
2898             break;
2899         }
2900 
2901         case ABS: {
2902             return static_cast<int>(std::abs(LHS()->Eval(context)));
2903             break;
2904         }
2905 
2906         case LOGARITHM: {
2907             double op1 = LHS()->Eval(context);
2908             if (op1 <= 0.0)
2909                 return 0;
2910             return static_cast<int>(std::log(op1));
2911             break;
2912         }
2913 
2914         case SINE: {
2915             double op1 = LHS()->Eval(context);
2916             return static_cast<int>(std::sin(op1));
2917             break;
2918         }
2919 
2920         case COSINE: {
2921             double op1 = LHS()->Eval(context);
2922             return static_cast<int>(std::cos(op1));
2923             break;
2924         }
2925 
2926         case MINIMUM:
2927         case MAXIMUM: {
2928             std::set<int> vals;
2929             for (auto& vr : m_operands) {
2930                 if (vr)
2931                     vals.insert(vr->Eval(context));
2932             }
2933             if (m_op_type == MINIMUM)
2934                 return vals.empty() ? 0 : *vals.begin();
2935             else
2936                 return vals.empty() ? 0 : *vals.rbegin();
2937             break;
2938         }
2939 
2940         case RANDOM_UNIFORM: {
2941             double op1 = LHS()->Eval(context);
2942             double op2 = RHS()->Eval(context);
2943             int min_val = static_cast<int>(std::min(op1, op2));
2944             int max_val = static_cast<int>(std::max(op1, op2));
2945             return RandInt(min_val, max_val);
2946             break;
2947         }
2948 
2949         case RANDOM_PICK: {
2950             // select one operand, evaluate it, return result
2951             if (m_operands.empty())
2952                 return 0;
2953             unsigned int idx = RandSmallInt(0, m_operands.size() - 1);
2954             auto& vr = *std::next(m_operands.begin(), idx);
2955             if (!vr)
2956                 return 0;
2957             return vr->Eval(context);
2958             break;
2959         }
2960 
2961         case ROUND_NEAREST:
2962         case ROUND_UP:
2963         case ROUND_DOWN: {
2964             // integers don't need to be rounded...
2965             return LHS()->Eval(context);
2966             break;
2967         }
2968 
2969         case COMPARE_EQUAL:
2970         case COMPARE_GREATER_THAN:
2971         case COMPARE_GREATER_THAN_OR_EQUAL:
2972         case COMPARE_LESS_THAN:
2973         case COMPARE_LESS_THAN_OR_EQUAL:
2974         case COMPARE_NOT_EQUAL: {
2975             const int&& lhs_val = LHS()->Eval(context);
2976             const int&& rhs_val = RHS()->Eval(context);
2977             bool test_result = false;
2978             switch (m_op_type) {
2979                 case COMPARE_EQUAL:                 test_result = lhs_val == rhs_val;   break;
2980                 case COMPARE_GREATER_THAN:          test_result = lhs_val > rhs_val;    break;
2981                 case COMPARE_GREATER_THAN_OR_EQUAL: test_result = lhs_val >= rhs_val;   break;
2982                 case COMPARE_LESS_THAN:             test_result = lhs_val < rhs_val;    break;
2983                 case COMPARE_LESS_THAN_OR_EQUAL:    test_result = lhs_val <= rhs_val;   break;
2984                 case COMPARE_NOT_EQUAL:             test_result = lhs_val != rhs_val;   break;
2985                 default:    break;  // ??? do nothing, default to false
2986             }
2987             if (m_operands.size() < 3) {
2988                 return static_cast<int>(test_result);
2989             } else if (m_operands.size() < 4) {
2990                 if (test_result)
2991                     return m_operands[2]->Eval(context);
2992                 else
2993                     return 0;
2994             } else {
2995                 if (test_result)
2996                     return m_operands[2]->Eval(context);
2997                 else
2998                     return m_operands[3]->Eval(context);
2999             }
3000         }
3001 
3002         default:    break;
3003     }
3004 
3005     throw std::runtime_error("double ValueRef evaluated with an unknown or invalid OpType.");
3006     return 0;
3007 }
3008 } // namespace ValueRef
3009