1 #include "SystemResourceSummaryBrowseWnd.h"
2 
3 #include "../util/i18n.h"
4 #include "../util/Logger.h"
5 #include "../universe/ResourceCenter.h"
6 #include "../universe/System.h"
7 #include "../universe/Enums.h"
8 #include "../Empire/Empire.h"
9 #include "../client/human/HumanClientApp.h"
10 #include "CUIControls.h"
11 
12 namespace {
13     /** Returns how much of specified \a resource_type is being consumed by the
14       * empire with id \a empire_id at the location of the specified
15       * object \a obj. */
ObjectResourceConsumption(std::shared_ptr<const UniverseObject> obj,ResourceType resource_type,int empire_id=ALL_EMPIRES)16     double ObjectResourceConsumption(std::shared_ptr<const UniverseObject> obj,
17                                      ResourceType resource_type,
18                                      int empire_id = ALL_EMPIRES)
19     {
20         if (!obj) {
21             ErrorLogger() << "ObjectResourceConsumption passed a null object";
22             return 0.0;
23         }
24         if (resource_type == INVALID_RESOURCE_TYPE) {
25             ErrorLogger() << "ObjectResourceConsumption passed a INVALID_RESOURCE_TYPE";
26             return 0.0;
27         }
28 
29 
30         const Empire* empire = nullptr;
31 
32         if (empire_id != ALL_EMPIRES) {
33             empire = GetEmpire(empire_id);
34 
35             if (!empire) {
36                 ErrorLogger() << "ObjectResourceConsumption requested consumption for empire " << empire_id << " but this empire was not found";
37                 return 0.0;     // requested a specific empire, but didn't find it in this client, so production is 0.0
38             }
39 
40             if (!obj->OwnedBy(empire_id)) {
41                 DebugLogger() << "ObjectResourceConsumption requested consumption for empire " << empire_id << " but this empire doesn't own the object";
42                 return 0.0;     // if the empire doesn't own the object, assuming it can't be consuming any of the empire's resources.  May need to revisit this assumption later.
43             }
44         }
45 
46 
47         //std::shared_ptr<const PopCenter> pc;
48         double prod_queue_allocation_sum = 0.0;
49         std::shared_ptr<const Building> building;
50 
51         switch (resource_type) {
52         case RE_INDUSTRY:
53             // PP (equal to mineral and industry) cost of objects on production queue at this object's location
54             if (empire) {
55                 // add allocated PP for all production items at this location for this empire
56                 for (const auto& elem : empire->GetProductionQueue())
57                     if (elem.location == obj->ID())
58                         prod_queue_allocation_sum += elem.allocated_pp;
59 
60             } else {
61                 // add allocated PP for all production items at this location for all empires
62                 for (auto& entry : Empires()) {
63                     empire = entry.second;
64                     for (const ProductionQueue::Element& elem : empire->GetProductionQueue())
65                         if (elem.location == obj->ID())
66                             prod_queue_allocation_sum += elem.allocated_pp;
67                 }
68             }
69             return prod_queue_allocation_sum;
70             break;
71 
72         case RE_TRADE:
73         case RE_RESEARCH:
74         case RE_STOCKPILE:
75             // research/stockpile aren't consumed at a particular location, so none is consumed at any location
76         default:
77             // for INVALID_RESOURCE_TYPE just return 0.0.  Could throw an exception, I suppose...
78             break;
79         }
80         return 0.0;
81     }
82 
83     const int       EDGE_PAD(3);
LabelWidth()84     GG::X LabelWidth()
85     { return GG::X(ClientUI::Pts()*18); }
86 
ValueWidth()87     GG::X ValueWidth()
88     { return GG::X(ClientUI::Pts()*4); }
89 }
90 
SystemResourceSummaryBrowseWnd(ResourceType resource_type,int system_id,int empire_id)91 SystemResourceSummaryBrowseWnd::SystemResourceSummaryBrowseWnd(ResourceType resource_type,
92                                                                int system_id, int empire_id) :
93     GG::BrowseInfoWnd(GG::X0, GG::Y0, LabelWidth() + ValueWidth(), GG::Y1),
94     m_resource_type(resource_type),
95     m_system_id(system_id),
96     m_empire_id(empire_id),
97     row_height(1),
98     production_label_top(0),
99     allocation_label_top(0),
100     import_export_label_top(0)
101 {}
102 
WndHasBrowseInfo(const GG::Wnd * wnd,std::size_t mode) const103 bool SystemResourceSummaryBrowseWnd::WndHasBrowseInfo(const GG::Wnd* wnd, std::size_t mode) const {
104     assert(mode <= wnd->BrowseModes().size());
105     return true;
106 }
107 
Render()108 void SystemResourceSummaryBrowseWnd::Render() {
109     GG::Pt ul = UpperLeft();
110     GG::Pt lr = LowerRight();
111     GG::FlatRectangle(ul, lr, OpaqueColor(ClientUI::WndColor()), ClientUI::WndOuterBorderColor(), 1);       // main background
112     GG::FlatRectangle(GG::Pt(ul.x, ul.y + production_label_top), GG::Pt(lr.x, ul.y + production_label_top + row_height),
113                       ClientUI::WndOuterBorderColor(), ClientUI::WndOuterBorderColor(), 0);                 // production label background
114     GG::FlatRectangle(GG::Pt(ul.x, ul.y + allocation_label_top), GG::Pt(lr.x, ul.y + allocation_label_top + row_height),
115                       ClientUI::WndOuterBorderColor(), ClientUI::WndOuterBorderColor(), 0);                 // allocation label background
116     GG::FlatRectangle(GG::Pt(ul.x, ul.y + import_export_label_top), GG::Pt(lr.x, ul.y + import_export_label_top + row_height),
117                       ClientUI::WndOuterBorderColor(), ClientUI::WndOuterBorderColor(), 0);                 // import or export label background
118 }
119 
UpdateImpl(std::size_t mode,const GG::Wnd * target)120 void SystemResourceSummaryBrowseWnd::UpdateImpl(std::size_t mode, const GG::Wnd* target) {
121     // fully recreate browse wnd for each viewing.  finding all the queues, resourcepools and (maybe?) individual
122     // UniverseObject that would have ChangedSignals that would need to be connected to the object that creates
123     // this BrowseWnd seems like more trouble than it's worth to avoid recreating the BrowseWnd every time it's shown
124     // (the alternative is to only reinitialize when something changes that would affect what's displayed in the
125     // BrowseWnd, which is how MeterBrowseWnd works)
126     Clear();
127     Initialize();
128 }
129 
Initialize()130 void SystemResourceSummaryBrowseWnd::Initialize() {
131     row_height = GG::Y(ClientUI::Pts() * 3/2);
132     const GG::X TOTAL_WIDTH = LabelWidth() + ValueWidth();
133 
134     GG::Y top = GG::Y0;
135 
136 
137     production_label_top = top;
138     m_production_label = GG::Wnd::Create<CUILabel>("", GG::FORMAT_RIGHT);
139     m_production_label->MoveTo(GG::Pt(GG::X0, production_label_top));
140     m_production_label->Resize(GG::Pt(TOTAL_WIDTH - EDGE_PAD, row_height));
141     m_production_label->SetFont(ClientUI::GetBoldFont());
142     AttachChild(m_production_label);
143     top += row_height;
144     UpdateProduction(top);
145 
146 
147     allocation_label_top = top;
148     m_allocation_label = GG::Wnd::Create<CUILabel>("", GG::FORMAT_RIGHT);
149     m_allocation_label->MoveTo(GG::Pt(GG::X0, allocation_label_top));
150     m_allocation_label->Resize(GG::Pt(TOTAL_WIDTH - EDGE_PAD, row_height));
151     m_allocation_label->SetFont(ClientUI::GetBoldFont());
152     AttachChild(m_allocation_label);
153     top += row_height;
154     UpdateAllocation(top);
155 
156 
157     import_export_label_top = top;
158     m_import_export_label = GG::Wnd::Create<CUILabel>("", GG::FORMAT_RIGHT);
159     m_import_export_label->MoveTo(GG::Pt(GG::X0, import_export_label_top));
160     m_import_export_label->Resize(GG::Pt(TOTAL_WIDTH - EDGE_PAD, row_height));
161     m_import_export_label->SetFont(ClientUI::GetBoldFont());
162     AttachChild(m_import_export_label);
163     top += row_height;
164     UpdateImportExport(top);
165 
166 
167     Resize(GG::Pt(LabelWidth() + ValueWidth(), top));
168 }
169 
UpdateProduction(GG::Y & top)170 void SystemResourceSummaryBrowseWnd::UpdateProduction(GG::Y& top) {
171     // adds pairs of labels for ResourceCenter name and production of resource starting at vertical position \a top
172     // and updates \a top to the vertical position after the last entry
173     for (const auto& label_pair : m_production_labels_and_amounts) {
174         DetachChild(label_pair.first);
175         DetachChild(label_pair.second);
176     }
177     m_production_labels_and_amounts.clear();
178 
179     auto system = Objects().get<System>(m_system_id);
180     if (!system || m_resource_type == INVALID_RESOURCE_TYPE)
181         return;
182 
183 
184     m_production = 0.0;
185 
186 
187     // add label-value pair for each resource-producing object in system to indicate amount of resource produced
188     auto objects = Objects().find<const UniverseObject>(system->ContainedObjectIDs());
189 
190     for (auto& obj : objects) {
191         // display information only for the requested player
192         if (m_empire_id != ALL_EMPIRES && !obj->OwnedBy(m_empire_id))
193             continue;   // if m_empire_id == -1, display resource production for all empires.  otherwise, skip this resource production if it's not owned by the requested player
194 
195         auto rc = std::dynamic_pointer_cast<const ResourceCenter>(obj);
196         if (!rc) continue;
197 
198         std::string name = obj->Name();
199         double production = rc->GetMeter(ResourceToMeter(m_resource_type))->Initial();
200         m_production += production;
201 
202         std::string amount_text = DoubleToString(production, 3, false);
203 
204 
205         auto label = GG::Wnd::Create<CUILabel>(name, GG::FORMAT_RIGHT);
206         label->MoveTo(GG::Pt(GG::X0, top));
207         label->Resize(GG::Pt(LabelWidth(), row_height));
208         AttachChild(label);
209 
210         auto value = GG::Wnd::Create<CUILabel>(amount_text);
211         value->MoveTo(GG::Pt(LabelWidth(), top));
212         value->Resize(GG::Pt(ValueWidth(), row_height));
213         AttachChild(value);
214 
215         m_production_labels_and_amounts.push_back({label, value});
216 
217         top += row_height;
218     }
219 
220 
221     if (m_production_labels_and_amounts.empty()) {
222         // add "blank" line to indicate no production
223         auto label = GG::Wnd::Create<CUILabel>(UserString("NOT_APPLICABLE"));
224         label->MoveTo(GG::Pt(GG::X0, top));
225         label->Resize(GG::Pt(LabelWidth(), row_height));
226         AttachChild(label);
227 
228         auto value = GG::Wnd::Create<CUILabel>("");
229         value->MoveTo(GG::Pt(LabelWidth(), top));
230         value->Resize(GG::Pt(ValueWidth(), row_height));
231         AttachChild(value);
232 
233         m_production_labels_and_amounts.push_back({label, value});
234 
235         top += row_height;
236     }
237 
238 
239     // set production label
240     std::string resource_text = "";
241     switch (m_resource_type) {
242     case RE_INDUSTRY:
243         resource_text = UserString("INDUSTRY_PRODUCTION");  break;
244     case RE_RESEARCH:
245         resource_text = UserString("RESEARCH_PRODUCTION");  break;
246     case RE_TRADE:
247         resource_text = UserString("TRADE_PRODUCTION");     break;
248     case RE_STOCKPILE:
249         resource_text = UserString("STOCKPILE_GENERATION"); break;
250     default:
251         resource_text = UserString("UNKNOWN_VALUE_SYMBOL"); break;
252     }
253 
254     m_production_label->SetText(boost::io::str(FlexibleFormat(UserString("RESOURCE_PRODUCTION_TOOLTIP")) %
255                                                               resource_text %
256                                                               DoubleToString(m_production, 3, false)));
257 
258     // height of label already added to top outside this function
259 }
260 
UpdateAllocation(GG::Y & top)261 void SystemResourceSummaryBrowseWnd::UpdateAllocation(GG::Y& top) {
262     // adds pairs of labels for allocation of resources in system, starting at vertical position \a top and
263     // updates \a top to be the vertical position after the last entry
264     for (const auto& label_pair : m_allocation_labels_and_amounts) {
265         DetachChild(label_pair.first);
266         DetachChild(label_pair.second);
267     }
268     m_allocation_labels_and_amounts.clear();
269 
270     auto system = Objects().get<System>(m_system_id);
271     if (!system || m_resource_type == INVALID_RESOURCE_TYPE)
272         return;
273 
274 
275     m_allocation = 0.0;
276 
277 
278     // add label-value pair for each resource-consuming object in system to indicate amount of resource consumed
279     for (auto& obj : Objects().find<const UniverseObject>(system->ContainedObjectIDs())) {
280         // display information only for the requested player
281         if (m_empire_id != ALL_EMPIRES && !obj->OwnedBy(m_empire_id))
282             continue;   // if m_empire_id == ALL_EMPIRES, display resource production for all empires.  otherwise, skip this resource production if it's not owned by the requested player
283 
284 
285         std::string name = obj->Name();
286 
287 
288         double allocation = ObjectResourceConsumption(obj, m_resource_type, m_empire_id);
289 
290 
291         // don't add summary entries for objects that consume no resource.  (otherwise there would be a loooong pointless list of 0's
292         if (allocation <= 0.0) {
293             if (allocation < 0.0)
294                 ErrorLogger() << "object " << obj->Name() << " is reported having negative " << m_resource_type << " consumption";
295             continue;
296         }
297 
298 
299         m_allocation += allocation;
300 
301         std::string amount_text = DoubleToString(allocation, 3, false);
302 
303 
304         auto label = GG::Wnd::Create<CUILabel>(name, GG::FORMAT_RIGHT);
305         label->MoveTo(GG::Pt(GG::X0, top));
306         label->Resize(GG::Pt(LabelWidth(), row_height));
307         AttachChild(label);
308 
309 
310         auto value = GG::Wnd::Create<CUILabel>(amount_text);
311         value->MoveTo(GG::Pt(LabelWidth(), top));
312         value->Resize(GG::Pt(ValueWidth(), row_height));
313         AttachChild(value);
314 
315         m_allocation_labels_and_amounts.push_back({label, value});
316 
317         top += row_height;
318     }
319 
320 
321     if (m_allocation_labels_and_amounts.empty()) {
322         // add "blank" line to indicate no allocation
323         auto label = GG::Wnd::Create<CUILabel>(UserString("NOT_APPLICABLE"), GG::FORMAT_RIGHT);
324         label->MoveTo(GG::Pt(GG::X0, top));
325         label->Resize(GG::Pt(LabelWidth(), row_height));
326         AttachChild(label);
327 
328         auto value = GG::Wnd::Create<CUILabel>("");
329         value->MoveTo(GG::Pt(LabelWidth(), top));
330         value->Resize(GG::Pt(ValueWidth(), row_height));
331         AttachChild(value);
332 
333         m_allocation_labels_and_amounts.push_back({label, value});
334 
335         top += row_height;
336     }
337 
338 
339     // set consumption / allocation label
340     std::string resource_text = "";
341     switch (m_resource_type) {
342     case RE_INDUSTRY:
343         resource_text = UserString("INDUSTRY_CONSUMPTION"); break;
344     case RE_RESEARCH:
345         resource_text = UserString("RESEARCH_CONSUMPTION"); break;
346     case RE_TRADE:
347         resource_text = UserString("TRADE_CONSUMPTION");    break;
348     case RE_STOCKPILE:
349         resource_text = UserString("STOCKPILE_USE");        break;
350     default:
351         resource_text = UserString("UNKNOWN_VALUE_SYMBOL"); break;
352     }
353 
354     std::string system_allocation_text = DoubleToString(m_allocation, 3, false);
355 
356     // for research and stockpiling, local allocation makes no sense
357     if (m_resource_type == RE_RESEARCH && m_allocation == 0.0)
358         system_allocation_text = UserString("NOT_APPLICABLE");
359     if (m_resource_type == RE_STOCKPILE && m_allocation == 0.0)
360         system_allocation_text = UserString("NOT_APPLICABLE");
361 
362 
363     m_allocation_label->SetText(boost::io::str(FlexibleFormat(UserString("RESOURCE_ALLOCATION_TOOLTIP")) %
364                                                               resource_text %
365                                                               system_allocation_text));
366 
367     // height of label already added to top outside this function
368 }
369 
UpdateImportExport(GG::Y & top)370 void SystemResourceSummaryBrowseWnd::UpdateImportExport(GG::Y& top) {
371     m_import_export_label->SetText(UserString("IMPORT_EXPORT_TOOLTIP"));
372 
373     const Empire* empire = nullptr;
374 
375     // check for early exit cases...
376     bool abort = false;
377     if (m_empire_id == ALL_EMPIRES ||
378         m_resource_type == RE_RESEARCH ||
379         m_resource_type == RE_STOCKPILE)
380     {
381         // multiple empires have complicated stockpiling which don't make sense to try to display.
382         // Research use is nonlocalized, so importing / exporting doesn't make sense to display
383         abort = true;
384     } else {
385         empire = GetEmpire(m_empire_id);
386         if (!empire)
387             abort = true;
388     }
389 
390 
391     std::string label_text = "", amount_text = "";
392 
393 
394     if (!abort) {
395         double difference = m_production - m_allocation;
396 
397         switch (m_resource_type) {
398         case RE_TRADE:
399         case RE_INDUSTRY:
400             if (difference > 0.0) {
401                 // show surplus
402                 label_text = UserString("RESOURCE_EXPORT");
403                 amount_text = DoubleToString(difference, 3, false);
404             } else if (difference < 0.0) {
405                 // show amount being imported
406                 label_text = UserString("RESOURCE_IMPORT");
407                 amount_text = DoubleToString(std::abs(difference), 3, false);
408             } else {
409                 // show self-sufficiency
410                 label_text = UserString("RESOURCE_SELF_SUFFICIENT");
411                 amount_text = "";
412             }
413             break;
414         case RE_RESEARCH:
415         case RE_STOCKPILE:
416         default:
417             // show nothing
418             abort = true;
419             break;
420         }
421     }
422 
423 
424     if (abort) {
425         label_text = UserString("NOT_APPLICABLE");
426         amount_text = "";   // no change
427     }
428 
429 
430     // add label and amount.  may be "NOT APPLIABLE" and nothing if aborted above
431     auto label = GG::Wnd::Create<CUILabel>(label_text, GG::FORMAT_RIGHT);
432     label->MoveTo(GG::Pt(GG::X0, top));
433     label->Resize(GG::Pt(LabelWidth(), row_height));
434     AttachChild(label);
435 
436     auto value = GG::Wnd::Create<CUILabel>(amount_text);
437     value->MoveTo(GG::Pt(LabelWidth(), top));
438     value->Resize(GG::Pt(ValueWidth(), row_height));
439     AttachChild(value);
440 
441     m_import_export_labels_and_amounts.push_back({label, value});
442 
443     top += row_height;
444 }
445 
Clear()446 void SystemResourceSummaryBrowseWnd::Clear() {
447     DetachChildAndReset(m_production_label);
448     DetachChildAndReset(m_allocation_label);
449     DetachChildAndReset(m_import_export_label);
450 
451     for (const auto& label_pair : m_production_labels_and_amounts) {
452         DetachChild(label_pair.first);
453         DetachChild(label_pair.second);
454     }
455     m_production_labels_and_amounts.clear();
456 
457     for (const auto& label_pair : m_allocation_labels_and_amounts) {
458         DetachChild(label_pair.first);
459         DetachChild(label_pair.second);
460     }
461     m_allocation_labels_and_amounts.clear();
462 
463     for (const auto& label_pair : m_import_export_labels_and_amounts) {
464         DetachChild(label_pair.first);
465         DetachChild(label_pair.second);
466     }
467     m_import_export_labels_and_amounts.clear();
468 }
469