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