1 #include "ShipPart.h"
2 
3 #include <boost/algorithm/string/case_conv.hpp>
4 
5 #include "Enums.h"
6 #include "ConditionSource.h"
7 #include "Effects.h"
8 #include "ValueRefs.h"
9 #include "../Empire/Empire.h"
10 #include "../Empire/EmpireManager.h"
11 #include "../util/CheckSums.h"
12 #include "../util/GameRules.h"
13 
14 
15 namespace {
16     const int ARBITRARY_LARGE_TURNS = 999999;
17     const float ARBITRARY_LARGE_COST = 999999.9f;
18 
19     // create effectsgroup that increases the value of \a meter_type
20     // by the result of evalulating \a increase_vr
21     std::shared_ptr<Effect::EffectsGroup>
IncreaseMeter(MeterType meter_type,std::unique_ptr<ValueRef::ValueRef<double>> && increase_vr)22     IncreaseMeter(MeterType meter_type,
23                   std::unique_ptr<ValueRef::ValueRef<double>>&& increase_vr)
24     {
25         typedef std::vector<std::unique_ptr<Effect::Effect>> Effects;
26         auto scope = std::make_unique<Condition::Source>();
27         auto activation = std::make_unique<Condition::Source>();
28 
29         auto vr =
30             std::make_unique<ValueRef::Operation<double>>(
31                 ValueRef::PLUS,
32                 std::make_unique<ValueRef::Variable<double>>(
33                     ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector<std::string>()),
34                 std::move(increase_vr)
35             );
36         auto effects = Effects();
37         effects.push_back(std::make_unique<Effect::SetMeter>(meter_type, std::move(vr)));
38         return std::make_shared<Effect::EffectsGroup>(std::move(scope), std::move(activation), std::move(effects));
39     }
40 
41     // create effectsgroup that increases the value of \a meter_type
42     // by the specified amount \a fixed_increase
43     std::shared_ptr<Effect::EffectsGroup>
IncreaseMeter(MeterType meter_type,float fixed_increase)44     IncreaseMeter(MeterType meter_type, float fixed_increase) {
45         auto increase_vr = std::make_unique<ValueRef::Constant<double>>(fixed_increase);
46         return IncreaseMeter(meter_type, std::move(increase_vr));
47     }
48 
49     // create effectsgroup that increases the value of the part meter
50     // of type \a meter_type for part name \a part_name
51     // by the result of evalulating \a increase_vr
52     std::shared_ptr<Effect::EffectsGroup>
IncreaseMeter(MeterType meter_type,const std::string & part_name,std::unique_ptr<ValueRef::ValueRef<double>> && increase_vr,bool allow_stacking=true)53     IncreaseMeter(MeterType meter_type, const std::string& part_name,
54                   std::unique_ptr<ValueRef::ValueRef<double>>&& increase_vr, bool allow_stacking = true)
55     {
56         typedef std::vector<std::unique_ptr<Effect::Effect>> Effects;
57         auto scope = std::make_unique<Condition::Source>();
58         auto activation = std::make_unique<Condition::Source>();
59 
60         auto value_vr = std::make_unique<ValueRef::Operation<double>>(
61             ValueRef::PLUS,
62             std::make_unique<ValueRef::Variable<double>>(
63                 ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector<std::string>()),
64             std::move(increase_vr)
65         );
66 
67         auto part_name_vr =
68             std::make_unique<ValueRef::Constant<std::string>>(part_name);
69 
70         std::string stacking_group = (allow_stacking ? "" :
71             (part_name + "_" + boost::lexical_cast<std::string>(meter_type) + "_PartMeter"));
72 
73         auto effects = Effects();
74         effects.push_back(std::make_unique<Effect::SetShipPartMeter>(
75                               meter_type, std::move(part_name_vr), std::move(value_vr)));
76 
77         return std::make_shared<Effect::EffectsGroup>(
78             std::move(scope), std::move(activation), std::move(effects), part_name, stacking_group);
79     }
80 
81     // create effectsgroup that increases the value of \a meter_type
82     // by the product of \a base_increase and the value of the game
83     // rule of type double with the name \a scaling_factor_rule_name
84     std::shared_ptr<Effect::EffectsGroup>
IncreaseMeterRuleScaled(MeterType meter_type,float base_increase,const std::string & scaling_factor_rule_name)85     IncreaseMeterRuleScaled(MeterType meter_type, float base_increase,
86                   const std::string& scaling_factor_rule_name)
87     {
88         // if no rule specified, revert to fixed constant increase
89         if (scaling_factor_rule_name.empty())
90             return IncreaseMeter(meter_type, base_increase);
91 
92         auto increase_vr = std::make_unique<ValueRef::Operation<double>>(
93             ValueRef::TIMES,
94             std::make_unique<ValueRef::Constant<double>>(base_increase),
95             std::make_unique<ValueRef::ComplexVariable<double>>(
96                 "GameRule", nullptr, nullptr, nullptr,
97                 std::make_unique<ValueRef::Constant<std::string>>(scaling_factor_rule_name)
98             )
99         );
100 
101         return IncreaseMeter(meter_type, std::move(increase_vr));
102     }
103 
104     // create effectsgroup that increases the value of the part meter
105     // of type \a meter_type for part name \a part_name by the fixed
106     // amount \a fixed_increase
107     std::shared_ptr<Effect::EffectsGroup>
IncreaseMeter(MeterType meter_type,const std::string & part_name,float fixed_increase,bool allow_stacking=true)108     IncreaseMeter(MeterType meter_type, const std::string& part_name,
109                   float fixed_increase, bool allow_stacking = true)
110     {
111         auto increase_vr = std::make_unique<ValueRef::Constant<double>>(fixed_increase);
112         return IncreaseMeter(meter_type, part_name, std::move(increase_vr), allow_stacking);
113     }
114 
115     // create effectsgroup that increases the value of the part meter
116     // of type \a meter_type for part name \a part_name by the fixed
117     // amount \a base_increase and the value of the game
118     // rule of type double with the name \a scaling_factor_rule_name
119     std::shared_ptr<Effect::EffectsGroup>
IncreaseMeterRuleScaled(MeterType meter_type,const std::string & part_name,float base_increase,const std::string & scaling_factor_rule_name,bool allow_stacking=true)120     IncreaseMeterRuleScaled(MeterType meter_type, const std::string& part_name,
121                   float base_increase, const std::string& scaling_factor_rule_name, bool allow_stacking = true)
122     {
123         // if no rule specified, revert to fixed constant increase
124         if (scaling_factor_rule_name.empty())
125             return IncreaseMeter(meter_type, part_name, base_increase, allow_stacking);
126 
127         auto increase_vr = std::make_unique<ValueRef::Operation<double>>(
128             ValueRef::TIMES,
129             std::make_unique<ValueRef::Constant<double>>(base_increase),
130             std::make_unique<ValueRef::ComplexVariable<double>>(
131                 "GameRule", nullptr, nullptr, nullptr,
132                 std::make_unique<ValueRef::Constant<std::string>>(scaling_factor_rule_name)
133             )
134         );
135 
136         return IncreaseMeter(meter_type, part_name, std::move(increase_vr), allow_stacking);
137     }
138 }
139 
140 
ShipPart()141 ShipPart::ShipPart() :
142     m_class(INVALID_SHIP_PART_CLASS)
143 {}
144 
ShipPart(ShipPartClass part_class,double capacity,double stat2,CommonParams & common_params,const MoreCommonParams & more_common_params,std::vector<ShipSlotType> mountable_slot_types,const std::string & icon,bool add_standard_capacity_effect,std::unique_ptr<Condition::Condition> && combat_targets)145 ShipPart::ShipPart(ShipPartClass part_class, double capacity, double stat2,
146                    CommonParams& common_params, const MoreCommonParams& more_common_params,
147                    std::vector<ShipSlotType> mountable_slot_types,
148                    const std::string& icon, bool add_standard_capacity_effect,
149                    std::unique_ptr<Condition::Condition>&& combat_targets) :
150     m_name(more_common_params.name),
151     m_description(more_common_params.description),
152     m_class(part_class),
153     m_capacity(capacity),
154     m_secondary_stat(stat2),
155     m_producible(common_params.producible),
156     m_production_cost(std::move(common_params.production_cost)),
157     m_production_time(std::move(common_params.production_time)),
158     m_mountable_slot_types(mountable_slot_types),
159     m_production_meter_consumption(std::move(common_params.production_meter_consumption)),
160     m_production_special_consumption(std::move(common_params.production_special_consumption)),
161     m_location(std::move(common_params.location)),
162     m_exclusions(more_common_params.exclusions),
163     m_icon(icon),
164     m_add_standard_capacity_effect(add_standard_capacity_effect),
165     m_combat_targets(std::move(combat_targets))
166 {
167     Init(std::move(common_params.effects));
168 
169     for (const std::string& tag : common_params.tags)
170         m_tags.insert(boost::to_upper_copy<std::string>(tag));
171 
172     TraceLogger() << "ShipPart::ShipPart: name: " << m_name
173                   << " description: " << m_description
174                   << " class: " << m_class
175                   << " capacity: " << m_capacity
176                   << " secondary stat: " << m_secondary_stat
177                   //<< " prod cost: " << m_production_cost
178                   //<< " prod time: " << m_production_time
179                   << " producible: " << m_producible
180                   //<< " mountable slot types: " << m_mountable_slot_types
181                   //<< " tags: " << m_tags
182                   //<< " prod meter consump: " << m_production_meter_consumption
183                   //<< " prod special consump: " << m_production_special_consumption
184                   //<< " location: " << m_location
185                   //<< " exclusions: " << m_exclusions
186                   //<< " effects: " << m_effects
187                   << " icon: " << m_icon
188                   << " add standard cap effect: " << m_add_standard_capacity_effect;
189 }
190 
Init(std::vector<std::unique_ptr<Effect::EffectsGroup>> && effects)191 void ShipPart::Init(std::vector<std::unique_ptr<Effect::EffectsGroup>>&& effects) {
192     if ((m_capacity != 0 || m_secondary_stat != 0) && m_add_standard_capacity_effect) {
193         switch (m_class) {
194         case PC_COLONY:
195         case PC_TROOPS:
196             m_effects.push_back(IncreaseMeter(METER_CAPACITY,                     m_name, m_capacity, false));
197             break;
198         case PC_FIGHTER_HANGAR: {   // capacity indicates how many fighters are stored in this type of part (combined for all copies of the part)
199             m_effects.push_back(IncreaseMeter(METER_MAX_CAPACITY,                 m_name, m_capacity, true));         // stacking capacities allowed for this part, so each part contributes to the total capacity
200             m_effects.push_back(IncreaseMeterRuleScaled(METER_MAX_SECONDARY_STAT, m_name, m_secondary_stat, "RULE_FIGHTER_DAMAGE_FACTOR",     false));  // stacking damage not allowed, as damage per shot should be the same regardless of number of shots
201             break;
202         }
203         case PC_FIGHTER_BAY: {      // capacity indicates how many fighters each instance of the part can launch per combat bout...
204             m_effects.push_back(IncreaseMeter(METER_MAX_CAPACITY,                 m_name, m_capacity, false));
205             m_effects.push_back(IncreaseMeter(METER_MAX_SECONDARY_STAT,           m_name, m_secondary_stat, false));
206             break;
207         }
208         case PC_DIRECT_WEAPON: {    // capacity indicates weapon damage per shot
209             m_effects.push_back(IncreaseMeterRuleScaled(METER_MAX_CAPACITY,       m_name, m_capacity,       "RULE_SHIP_WEAPON_DAMAGE_FACTOR", false));
210             m_effects.push_back(IncreaseMeter(METER_MAX_SECONDARY_STAT,           m_name, m_secondary_stat, false));
211             break;
212         }
213         case PC_SHIELD:
214             m_effects.push_back(IncreaseMeterRuleScaled(METER_MAX_SHIELD,    m_capacity,     "RULE_SHIP_WEAPON_DAMAGE_FACTOR"));
215             break;
216         case PC_DETECTION:
217             m_effects.push_back(IncreaseMeter(METER_DETECTION,               m_capacity));
218             break;
219         case PC_STEALTH:
220             m_effects.push_back(IncreaseMeter(METER_STEALTH,                 m_capacity));
221             break;
222         case PC_FUEL:
223             m_effects.push_back(IncreaseMeter(METER_MAX_FUEL,                m_capacity));
224             break;
225         case PC_ARMOUR:
226             m_effects.push_back(IncreaseMeterRuleScaled(METER_MAX_STRUCTURE, m_capacity,     "RULE_SHIP_STRUCTURE_FACTOR"));
227             break;
228         case PC_SPEED:
229             m_effects.push_back(IncreaseMeterRuleScaled(METER_SPEED,         m_capacity,     "RULE_SHIP_SPEED_FACTOR"));
230             break;
231         case PC_RESEARCH:
232             m_effects.push_back(IncreaseMeter(METER_TARGET_RESEARCH,         m_capacity));
233             break;
234         case PC_INDUSTRY:
235             m_effects.push_back(IncreaseMeter(METER_TARGET_INDUSTRY,         m_capacity));
236             break;
237         case PC_TRADE:
238             m_effects.push_back(IncreaseMeter(METER_TARGET_TRADE,            m_capacity));
239             break;
240         default:
241             break;
242         }
243     }
244 
245     if (m_production_cost)
246         m_production_cost->SetTopLevelContent(m_name);
247     if (m_production_time)
248         m_production_time->SetTopLevelContent(m_name);
249     if (m_location)
250         m_location->SetTopLevelContent(m_name);
251     if (m_combat_targets)
252         m_combat_targets->SetTopLevelContent(m_name);
253     for (auto&& effect : effects) {
254         effect->SetTopLevelContent(m_name);
255         m_effects.emplace_back(std::move(effect));
256     }
257 }
258 
~ShipPart()259 ShipPart::~ShipPart()
260 {}
261 
Capacity() const262 float ShipPart::Capacity() const {
263     switch (m_class) {
264     case PC_ARMOUR:
265         return m_capacity * GetGameRules().Get<double>("RULE_SHIP_STRUCTURE_FACTOR");
266         break;
267     case PC_DIRECT_WEAPON:
268     case PC_SHIELD:
269         return m_capacity * GetGameRules().Get<double>("RULE_SHIP_WEAPON_DAMAGE_FACTOR");
270         break;
271     case PC_SPEED:
272         return m_capacity * GetGameRules().Get<double>("RULE_SHIP_SPEED_FACTOR");
273         break;
274     default:
275         return m_capacity;
276     }
277 }
278 
SecondaryStat() const279 float ShipPart::SecondaryStat() const {
280     switch (m_class) {
281     case PC_FIGHTER_HANGAR:
282         return m_capacity * GetGameRules().Get<double>("RULE_FIGHTER_DAMAGE_FACTOR");
283         break;
284     default:
285         return m_secondary_stat;
286     }
287 }
288 
CapacityDescription() const289 std::string ShipPart::CapacityDescription() const {
290     std::string desc_string;
291     float main_stat = Capacity();
292     float sdry_stat = SecondaryStat();
293 
294     switch (m_class) {
295     case PC_FUEL:
296     case PC_TROOPS:
297     case PC_COLONY:
298     case PC_FIGHTER_BAY:
299         desc_string += str(FlexibleFormat(UserString("PART_DESC_CAPACITY")) % main_stat);
300         break;
301     case PC_DIRECT_WEAPON:
302         desc_string += str(FlexibleFormat(UserString("PART_DESC_DIRECT_FIRE_STATS")) % main_stat % sdry_stat);
303         break;
304     case PC_FIGHTER_HANGAR:
305         desc_string += str(FlexibleFormat(UserString("PART_DESC_HANGAR_STATS")) % main_stat % sdry_stat);
306         break;
307     case PC_SHIELD:
308         desc_string = str(FlexibleFormat(UserString("PART_DESC_SHIELD_STRENGTH")) % main_stat);
309         break;
310     case PC_DETECTION:
311         desc_string = str(FlexibleFormat(UserString("PART_DESC_DETECTION")) % main_stat);
312         break;
313     default:
314         desc_string = str(FlexibleFormat(UserString("PART_DESC_STRENGTH")) % main_stat);
315         break;
316     }
317     return desc_string;
318 }
319 
CanMountInSlotType(ShipSlotType slot_type) const320 bool ShipPart::CanMountInSlotType(ShipSlotType slot_type) const {
321     if (INVALID_SHIP_SLOT_TYPE == slot_type)
322         return false;
323     for (ShipSlotType mountable_slot_type : m_mountable_slot_types)
324         if (mountable_slot_type == slot_type)
325             return true;
326     return false;
327 }
328 
ProductionCostTimeLocationInvariant() const329 bool ShipPart::ProductionCostTimeLocationInvariant() const {
330     if (GetGameRules().Get<bool>("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION"))
331         return true;
332     if (m_production_cost && !m_production_cost->TargetInvariant())
333         return false;
334     if (m_production_time && !m_production_time->TargetInvariant())
335         return false;
336     return true;
337 }
338 
ProductionCost(int empire_id,int location_id,int in_design_id) const339 float ShipPart::ProductionCost(int empire_id, int location_id, int in_design_id) const {
340     if (GetGameRules().Get<bool>("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_cost)
341         return 1.0f;
342 
343     if (m_production_cost->ConstantExpr()) {
344         return static_cast<float>(m_production_cost->Eval());
345     } else if (m_production_cost->SourceInvariant() && m_production_cost->TargetInvariant()) {
346         ScriptingContext context(nullptr, nullptr, in_design_id);
347         return static_cast<float>(m_production_cost->Eval(context));
348     }
349 
350     auto location = Objects().get(location_id);
351     if (!location && !m_production_cost->TargetInvariant())
352         return ARBITRARY_LARGE_COST;
353 
354     auto source = Empires().GetSource(empire_id);
355     if (!source && !m_production_cost->SourceInvariant())
356         return ARBITRARY_LARGE_COST;
357 
358     ScriptingContext context(source, location, in_design_id);
359     return static_cast<float>(m_production_cost->Eval(context));
360 }
361 
ProductionTime(int empire_id,int location_id,int in_design_id) const362 int ShipPart::ProductionTime(int empire_id, int location_id, int in_design_id) const {
363     if (GetGameRules().Get<bool>("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_time)
364         return 1;
365 
366     if (m_production_time->ConstantExpr()) {
367         return m_production_time->Eval();
368     } else if (m_production_time->SourceInvariant() && m_production_time->TargetInvariant()) {
369         ScriptingContext context(nullptr, nullptr, in_design_id);
370         return m_production_time->Eval(context);
371     }
372 
373     auto location = Objects().get(location_id);
374     if (!location && !m_production_time->TargetInvariant())
375         return ARBITRARY_LARGE_TURNS;
376 
377     auto source = Empires().GetSource(empire_id);
378     if (!source && !m_production_time->SourceInvariant())
379         return ARBITRARY_LARGE_TURNS;
380 
381     ScriptingContext context(source, location, in_design_id);
382     return m_production_time->Eval(context);
383 }
384 
GetCheckSum() const385 unsigned int ShipPart::GetCheckSum() const {
386     unsigned int retval{0};
387 
388     CheckSums::CheckSumCombine(retval, m_name);
389     CheckSums::CheckSumCombine(retval, m_description);
390     CheckSums::CheckSumCombine(retval, m_class);
391     CheckSums::CheckSumCombine(retval, m_capacity);
392     CheckSums::CheckSumCombine(retval, m_secondary_stat);
393     CheckSums::CheckSumCombine(retval, m_production_cost);
394     CheckSums::CheckSumCombine(retval, m_production_time);
395     CheckSums::CheckSumCombine(retval, m_producible);
396     CheckSums::CheckSumCombine(retval, m_mountable_slot_types);
397     CheckSums::CheckSumCombine(retval, m_tags);
398     CheckSums::CheckSumCombine(retval, m_production_meter_consumption);
399     CheckSums::CheckSumCombine(retval, m_production_special_consumption);
400     CheckSums::CheckSumCombine(retval, m_location);
401     CheckSums::CheckSumCombine(retval, m_exclusions);
402     CheckSums::CheckSumCombine(retval, m_effects);
403     CheckSums::CheckSumCombine(retval, m_icon);
404     CheckSums::CheckSumCombine(retval, m_add_standard_capacity_effect);
405 
406     return retval;
407 }
408 
409 
410 ShipPartManager* ShipPartManager::s_instance = nullptr;
411 
ShipPartManager()412 ShipPartManager::ShipPartManager() {
413     if (s_instance)
414         throw std::runtime_error("Attempted to create more than one ShipPartManager.");
415 
416     // Only update the global pointer on sucessful construction.
417     s_instance = this;
418 }
419 
GetShipPart(const std::string & name) const420 const ShipPart* ShipPartManager::GetShipPart(const std::string& name) const {
421     CheckPendingShipParts();
422     auto it = m_parts.find(name);
423     return it != m_parts.end() ? it->second.get() : nullptr;
424 }
425 
GetShipPartManager()426 ShipPartManager& ShipPartManager::GetShipPartManager() {
427     static ShipPartManager manager;
428     return manager;
429 }
430 
begin() const431 ShipPartManager::iterator ShipPartManager::begin() const {
432     CheckPendingShipParts();
433     return m_parts.begin();
434 }
435 
end() const436 ShipPartManager::iterator ShipPartManager::end() const{
437     CheckPendingShipParts();
438     return m_parts.end();
439 }
440 
GetCheckSum() const441 unsigned int ShipPartManager::GetCheckSum() const {
442     CheckPendingShipParts();
443     unsigned int retval{0};
444     for (auto const& name_part_pair : m_parts)
445         CheckSums::CheckSumCombine(retval, name_part_pair);
446     CheckSums::CheckSumCombine(retval, m_parts.size());
447 
448 
449     DebugLogger() << "ShipPartManager checksum: " << retval;
450     return retval;
451 }
452 
SetShipParts(Pending::Pending<ShipPartMap> && pending_ship_parts)453 void ShipPartManager::SetShipParts(Pending::Pending<ShipPartMap>&& pending_ship_parts)
454 { m_pending_ship_parts = std::move(pending_ship_parts); }
455 
CheckPendingShipParts() const456 void ShipPartManager::CheckPendingShipParts() const {
457     if (!m_pending_ship_parts)
458         return;
459 
460     Pending::SwapPending(m_pending_ship_parts, m_parts);
461 
462     TraceLogger() << [this]() {
463             std::string retval("Part Types:");
464             for (const auto& pair : m_parts) {
465                 const auto& part = pair.second;
466                 retval.append("\n\t" + part->Name() + " class: " + boost::lexical_cast<std::string>(part->Class()));
467             }
468             return retval;
469         }();
470 }
471 
GetShipPartManager()472 ShipPartManager& GetShipPartManager()
473 { return ShipPartManager::GetShipPartManager(); }
474 
GetShipPart(const std::string & name)475 const ShipPart* GetShipPart(const std::string& name)
476 { return GetShipPartManager().GetShipPart(name); }
477