Merge lp:~widelands-dev/widelands/bug-1573057-graph-relative-plotmode into lp:widelands
- bug-1573057-graph-relative-plotmode
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 8022 | ||||||||
Proposed branch: | lp:~widelands-dev/widelands/bug-1573057-graph-relative-plotmode | ||||||||
Merge into: | lp:widelands | ||||||||
Diff against target: |
918 lines (+231/-248) 6 files modified
src/logic/cmd_calculate_statistics.cc (+1/-1) src/logic/constants.h (+4/-2) src/wui/general_statistics_menu.cc (+3/-3) src/wui/plot_area.cc (+176/-203) src/wui/plot_area.h (+35/-27) src/wui/ware_statistics_menu.cc (+12/-12) |
||||||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/bug-1573057-graph-relative-plotmode | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
TiborB | Approve | ||
kaputtnik (community) | testing | Approve | |
Review via email: mp+295198@code.launchpad.net |
Commit message
Fixed relative plot mode and flickering graphs on button click. Only draw active statistics tab. Some refactoring.
Description of the change
GunChleoc (gunchleoc) wrote : | # |
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 1139. State: failed. Details: https:/
Appveyor build 976. State: failed. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 1143. State: passed. Details: https:/
Appveyor build 980. State: failed. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 1143. State: passed. Details: https:/
Appveyor build 980. State: success. Details: https:/
kaputtnik (franku) wrote : | # |
Works as expected :-)
bunnybot (widelandsofficial) wrote : | # |
Bunnybot encountered an error while working on this merge proposal:
<urlopen error _ssl.c:495: The handshake operation timed out>
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 1143. State: passed. Details: https:/
Appveyor build 980. State: success. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Bunnybot encountered an error while working on this merge proposal:
The read operation timed out
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 1143. State: passed. Details: https:/
Appveyor build 980. State: success. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 1156. State: failed. Details: https:/
Appveyor build 994. State: success. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Bunnybot encountered an error while working on this merge proposal:
<urlopen error _ssl.c:495: The handshake operation timed out>
bunnybot (widelandsofficial) wrote : | # |
Bunnybot encountered an error while working on this merge proposal:
<urlopen error _ssl.c:495: The handshake operation timed out>
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 1156. State: failed. Details: https:/
Appveyor build 994. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
Ping?
It would be good to get this branch in so that we can go into official feature freeze.
TiborB (tiborb95) wrote : | # |
oh, too complicated for me, but it at least LOOKS good to me :)
GunChleoc (gunchleoc) wrote : | # |
Now you know how I feel when I review your AI code *lol* Thanks!
@bunnybot merge
Preview Diff
1 | === modified file 'src/logic/cmd_calculate_statistics.cc' |
2 | --- src/logic/cmd_calculate_statistics.cc 2015-10-24 15:42:37 +0000 |
3 | +++ src/logic/cmd_calculate_statistics.cc 2016-06-10 17:28:35 +0000 |
4 | @@ -31,7 +31,7 @@ |
5 | game.sample_statistics(); |
6 | game.enqueue_command |
7 | (new CmdCalculateStatistics |
8 | - (game.get_gametime() + STATISTICS_SAMPLE_TIME)); |
9 | + (game.get_gametime() + kStatisticsSampleTime)); |
10 | } |
11 | |
12 | constexpr uint16_t kCurrentPacketVersion = 1; |
13 | |
14 | === modified file 'src/logic/constants.h' |
15 | --- src/logic/constants.h 2015-03-01 09:21:20 +0000 |
16 | +++ src/logic/constants.h 2016-06-10 17:28:35 +0000 |
17 | @@ -1,5 +1,5 @@ |
18 | /* |
19 | - * Copyright (C) 2006-2015 by the Widelands Development Team |
20 | + * Copyright (C) 2006-2016 by the Widelands Development Team |
21 | * |
22 | * This program is free software; you can redistribute it and/or |
23 | * modify it under the terms of the GNU General Public License |
24 | @@ -20,6 +20,8 @@ |
25 | #ifndef WL_LOGIC_CONSTANTS_H |
26 | #define WL_LOGIC_CONSTANTS_H |
27 | |
28 | +#include <cstdint> |
29 | + |
30 | /// Maximum numbers of players in a game. The game logic code reserves 5 bits |
31 | /// for player numbers, so it can keep track of 32 different player numbers, of |
32 | /// which the value 0 means neutral and the values 1 .. 31 can be used as the |
33 | @@ -27,6 +29,6 @@ |
34 | #define MAX_PLAYERS 8 |
35 | |
36 | /// How often are statistics to be sampled. |
37 | -#define STATISTICS_SAMPLE_TIME 30000 |
38 | +constexpr uint32_t kStatisticsSampleTime = 30000; |
39 | |
40 | #endif // end of include guard: WL_LOGIC_CONSTANTS_H |
41 | |
42 | === modified file 'src/wui/general_statistics_menu.cc' |
43 | --- src/wui/general_statistics_menu.cc 2016-01-29 08:37:22 +0000 |
44 | +++ src/wui/general_statistics_menu.cc 2016-06-10 17:28:35 +0000 |
45 | @@ -67,7 +67,9 @@ |
46 | 440, 400, _("General Statistics")), |
47 | my_registry_ (®istry), |
48 | box_ (this, 0, 0, UI::Box::Vertical, 0, 0, 5), |
49 | - plot_ (&box_, 0, 0, 430, PLOT_HEIGHT), |
50 | + plot_ (&box_, 0, 0, 430, PLOT_HEIGHT, |
51 | + kStatisticsSampleTime, |
52 | + WuiPlotArea::Plotmode::kAbsolute), |
53 | selected_information_(0) |
54 | { |
55 | assert (my_registry_); |
56 | @@ -78,8 +80,6 @@ |
57 | box_.set_border(5, 5, 5, 5); |
58 | |
59 | // Setup plot data |
60 | - plot_.set_sample_rate(STATISTICS_SAMPLE_TIME); |
61 | - plot_.set_plotmode(WuiPlotArea::PLOTMODE_ABSOLUTE); |
62 | Game & game = *parent.get_game(); |
63 | const Game::GeneralStatsVector & genstats = |
64 | game.get_general_statistics(); |
65 | |
66 | === modified file 'src/wui/plot_area.cc' |
67 | --- src/wui/plot_area.cc 2016-03-29 17:28:15 +0000 |
68 | +++ src/wui/plot_area.cc 2016-06-10 17:28:35 +0000 |
69 | @@ -39,24 +39,24 @@ |
70 | |
71 | constexpr int32_t kUpdateTimeInGametimeMs = 1000; // 1 second, gametime |
72 | |
73 | -const uint32_t minutes = 60 * 1000; |
74 | -const uint32_t hours = 60 * 60 * 1000; |
75 | -const uint32_t days = 24 * 60 * 60 * 1000; |
76 | +constexpr uint32_t kMinutes = 60 * 1000; |
77 | +constexpr uint32_t kHours = 60 * 60 * 1000; |
78 | +constexpr uint32_t kDays = 24 * 60 * 60 * 1000; |
79 | |
80 | -const int32_t spacing = 5; |
81 | -const int32_t space_at_bottom = 20; |
82 | -const int32_t space_at_right = 10; |
83 | -const int32_t space_left_of_label = 15; |
84 | -const uint32_t nr_samples = 30; // How many samples per diagramm when relative plotting |
85 | +constexpr int32_t kSpacing = 5; |
86 | +constexpr int32_t kSpaceBottom = 20; |
87 | +constexpr int32_t kSpaceRight = 10; |
88 | +constexpr int32_t kSpaceLeftOfLabel = 15; |
89 | +constexpr uint32_t KNoSamples = 30; // How many samples per diagramm when relative plotting |
90 | |
91 | const uint32_t time_in_ms[] = { |
92 | - 15 * minutes, |
93 | - 30 * minutes, |
94 | - 1 * hours, |
95 | - 2 * hours, |
96 | - 5 * hours, |
97 | - 10 * hours, |
98 | - 30 * hours |
99 | + 15 * kMinutes, |
100 | + 30 * kMinutes, |
101 | + 1 * kHours, |
102 | + 2 * kHours, |
103 | + 5 * kHours, |
104 | + 10 * kHours, |
105 | + 30 * kHours |
106 | }; |
107 | |
108 | const char BG_PIC[] = "images/wui/plot_area_bg.png"; |
109 | @@ -99,18 +99,18 @@ |
110 | Units get_suggested_unit(uint32_t game_time, bool is_generic = false) { |
111 | // Find a nice unit for max_x |
112 | if (is_generic) { |
113 | - if (game_time > 4 * days) { |
114 | + if (game_time > 4 * kDays) { |
115 | return Units::kDayGeneric; |
116 | - } else if (game_time > 4 * hours) { |
117 | + } else if (game_time > 4 * kHours) { |
118 | return Units::kHourGeneric; |
119 | } else { |
120 | return Units::kMinutesGeneric; |
121 | } |
122 | } |
123 | else { |
124 | - if (game_time > 4 * days) { |
125 | + if (game_time > 4 * kDays) { |
126 | return Units::kDayNarrow; |
127 | - } else if (game_time > 4 * hours) { |
128 | + } else if (game_time > 4 * kHours) { |
129 | return Units::kHourNarrow; |
130 | } else { |
131 | return Units::kMinutesNarrow; |
132 | @@ -153,13 +153,13 @@ |
133 | switch (unit) { |
134 | case Units::kDayGeneric: |
135 | case Units::kDayNarrow: |
136 | - return ms / days; |
137 | + return ms / kDays; |
138 | case Units::kHourGeneric: |
139 | case Units::kHourNarrow: |
140 | - return ms / hours; |
141 | + return ms / kHours; |
142 | case Units::kMinutesGeneric: |
143 | case Units::kMinutesNarrow: |
144 | - return ms / minutes; |
145 | + return ms / kMinutes; |
146 | } |
147 | NEVER_HERE(); |
148 | } |
149 | @@ -167,11 +167,11 @@ |
150 | /** |
151 | * calculate how many values are taken together when plot mode is relative |
152 | */ |
153 | -int32_t calc_how_many(uint32_t time_ms, int32_t sample_rate) { |
154 | +int32_t calc_how_many(uint32_t time_ms, uint32_t sample_rate) { |
155 | int32_t how_many = static_cast<int32_t> |
156 | ((static_cast<float>(time_ms) |
157 | / |
158 | - static_cast<float>(nr_samples)) |
159 | + static_cast<float>(KNoSamples)) |
160 | / |
161 | static_cast<float>(sample_rate)); |
162 | |
163 | @@ -230,28 +230,28 @@ |
164 | // Draw coordinate system |
165 | // X Axis |
166 | dst.draw_line_strip({ |
167 | - FloatPoint(spacing, inner_h - space_at_bottom), |
168 | - FloatPoint(inner_w - space_at_right, inner_h - space_at_bottom)}, |
169 | + FloatPoint(kSpacing, inner_h - kSpaceBottom), |
170 | + FloatPoint(inner_w - kSpaceRight, inner_h - kSpaceBottom)}, |
171 | kAxisLineColor, kAxisLinesWidth); |
172 | // Arrow |
173 | dst.draw_line_strip({ |
174 | - FloatPoint(spacing + 5, inner_h - space_at_bottom - 3), |
175 | - FloatPoint(spacing, inner_h - space_at_bottom), |
176 | - FloatPoint(spacing + 5, inner_h - space_at_bottom + 3), |
177 | + FloatPoint(kSpacing + 5, inner_h - kSpaceBottom - 3), |
178 | + FloatPoint(kSpacing, inner_h - kSpaceBottom), |
179 | + FloatPoint(kSpacing + 5, inner_h - kSpaceBottom + 3), |
180 | }, kAxisLineColor, kAxisLinesWidth); |
181 | |
182 | // Y Axis |
183 | - dst.draw_line_strip({FloatPoint(inner_w - space_at_right, spacing), |
184 | - FloatPoint(inner_w - space_at_right, inner_h - space_at_bottom)}, |
185 | + dst.draw_line_strip({FloatPoint(inner_w - kSpaceRight, kSpacing), |
186 | + FloatPoint(inner_w - kSpaceRight, inner_h - kSpaceBottom)}, |
187 | kAxisLineColor, kAxisLinesWidth); |
188 | // No Arrow here, since this doesn't continue. |
189 | |
190 | - float sub = (xline_length - space_left_of_label) / how_many_ticks; |
191 | - float posx = inner_w - space_at_right; |
192 | + float sub = (xline_length - kSpaceLeftOfLabel) / how_many_ticks; |
193 | + float posx = inner_w - kSpaceRight; |
194 | |
195 | for (uint32_t i = 0; i <= how_many_ticks; ++i) { |
196 | - dst.draw_line_strip({FloatPoint(static_cast<int32_t>(posx), inner_h - space_at_bottom), |
197 | - FloatPoint(static_cast<int32_t>(posx), inner_h - space_at_bottom + 3)}, |
198 | + dst.draw_line_strip({FloatPoint(static_cast<int32_t>(posx), inner_h - kSpaceBottom), |
199 | + FloatPoint(static_cast<int32_t>(posx), inner_h - kSpaceBottom + 3)}, |
200 | kAxisLineColor, kAxisLinesWidth); |
201 | |
202 | // The space at the end is intentional to have the tick centered |
203 | @@ -259,7 +259,7 @@ |
204 | const Image* xtick = UI::g_fh1->render |
205 | (xtick_text_style((boost::format("-%u ") % (max_x / how_many_ticks * i)).str())); |
206 | dst.blit |
207 | - (Point(static_cast<int32_t>(posx), inner_h - space_at_bottom + 10), |
208 | + (Point(static_cast<int32_t>(posx), inner_h - kSpaceBottom + 10), |
209 | xtick, BlendMode::UseAlpha, UI::Align::kCenter); |
210 | |
211 | posx -= sub; |
212 | @@ -267,29 +267,32 @@ |
213 | |
214 | // draw yticks, one at full, one at half |
215 | dst.draw_line_strip({ |
216 | - FloatPoint(inner_w - space_at_right, spacing), FloatPoint(inner_w - space_at_right - 3, spacing)}, |
217 | + FloatPoint(inner_w - kSpaceRight, kSpacing), FloatPoint(inner_w - kSpaceRight - 3, kSpacing)}, |
218 | kAxisLineColor, kAxisLinesWidth); |
219 | dst.draw_line_strip({ |
220 | - FloatPoint(inner_w - space_at_right, spacing + ((inner_h - space_at_bottom) - spacing) / 2), |
221 | - FloatPoint(inner_w - space_at_right - 3, spacing + ((inner_h - space_at_bottom) - spacing) / 2)}, |
222 | + FloatPoint(inner_w - kSpaceRight, kSpacing + ((inner_h - kSpaceBottom) - kSpacing) / 2), |
223 | + FloatPoint(inner_w - kSpaceRight - 3, kSpacing + ((inner_h - kSpaceBottom) - kSpacing) / 2)}, |
224 | kAxisLineColor, kAxisLinesWidth); |
225 | |
226 | // print the used unit |
227 | const Image* xtick = UI::g_fh1->render(xtick_text_style(get_generic_unit_name(unit))); |
228 | - dst.blit(Point(2, spacing + 2), xtick, BlendMode::UseAlpha, UI::Align::kCenterLeft); |
229 | + dst.blit(Point(2, kSpacing + 2), xtick, BlendMode::UseAlpha, UI::Align::kCenterLeft); |
230 | } |
231 | |
232 | } // namespace |
233 | |
234 | WuiPlotArea::WuiPlotArea |
235 | (UI::Panel * const parent, |
236 | - int32_t const x, int32_t const y, int32_t const w, int32_t const h) |
237 | + int32_t const x, int32_t const y, int32_t const w, int32_t const h, |
238 | + uint32_t sample_rate, Plotmode plotmode) |
239 | : |
240 | UI::Panel (parent, x, y, w, h), |
241 | -plotmode_(PLOTMODE_ABSOLUTE), |
242 | -sample_rate_(0), |
243 | +plotmode_(plotmode), |
244 | +sample_rate_(sample_rate), |
245 | needs_update_(true), |
246 | lastupdate_(0), |
247 | +xline_length_(get_inner_w() - kSpaceRight - kSpacing), |
248 | +yline_length_(get_inner_h() - kSpaceBottom - kSpacing), |
249 | time_ms_(0), |
250 | highest_scale_(0), |
251 | sub_(0.0f), |
252 | @@ -300,19 +303,19 @@ |
253 | } |
254 | |
255 | |
256 | -uint32_t WuiPlotArea::get_game_time() { |
257 | +uint32_t WuiPlotArea::get_game_time() const { |
258 | uint32_t game_time = 0; |
259 | |
260 | // Find running time of the game, based on the plot data |
261 | for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) |
262 | - if (game_time < plotdata_[plot].dataset->size() * sample_rate_) |
263 | - game_time = plotdata_[plot].dataset->size() * sample_rate_; |
264 | + if (game_time < plotdata_[plot].absolute_data->size() * sample_rate_) |
265 | + game_time = plotdata_[plot].absolute_data->size() * sample_rate_; |
266 | return game_time; |
267 | } |
268 | |
269 | -std::vector<std::string> WuiPlotArea::get_labels() { |
270 | +std::vector<std::string> WuiPlotArea::get_labels() const { |
271 | std::vector<std::string> labels; |
272 | - for (int32_t i = 0; i < game_time_id_; i++) { |
273 | + for (uint32_t i = 0; i < game_time_id_; i++) { |
274 | Units unit = get_suggested_unit(time_in_ms[i], false); |
275 | labels.push_back(get_value_with_unit(unit, ms_to_unit(unit, time_in_ms[i]))); |
276 | } |
277 | @@ -320,7 +323,7 @@ |
278 | return labels; |
279 | } |
280 | |
281 | -uint32_t WuiPlotArea::get_plot_time() { |
282 | +uint32_t WuiPlotArea::get_plot_time() const { |
283 | if (time_ == TIME_GAME) { |
284 | // Start with the game time |
285 | uint32_t time_ms = get_game_time(); |
286 | @@ -331,14 +334,14 @@ |
287 | // or a multiple of 2h |
288 | // or a multiple of 20h |
289 | // or a multiple of 4 days |
290 | - if (time_ms > 8 * days) { |
291 | - time_ms += - (time_ms % (4 * days)) + 4 * days; |
292 | - } else if (time_ms > 40 * hours) { |
293 | - time_ms += - (time_ms % (20 * hours)) + 20 * hours; |
294 | - } else if (time_ms > 4 * hours) { |
295 | - time_ms += - (time_ms % (2 * hours)) + 2 * hours; |
296 | + if (time_ms > 8 * kDays) { |
297 | + time_ms += - (time_ms % (4 * kDays)) + 4 * kDays; |
298 | + } else if (time_ms > 40 * kHours) { |
299 | + time_ms += - (time_ms % (20 * kHours)) + 20 * kHours; |
300 | + } else if (time_ms > 4 * kHours) { |
301 | + time_ms += - (time_ms % (2 * kHours)) + 2 * kHours; |
302 | } else { |
303 | - time_ms += - (time_ms % (15 * minutes)) + 15 * minutes; |
304 | + time_ms += - (time_ms % (15 * kMinutes)) + 15 * kMinutes; |
305 | } |
306 | return time_ms; |
307 | } else { |
308 | @@ -356,7 +359,7 @@ |
309 | * We start to search with i=1 to ensure that at least one option besides |
310 | * "game" will be offered to the user. |
311 | */ |
312 | -int32_t WuiPlotArea::get_game_time_id() { |
313 | +uint32_t WuiPlotArea::get_game_time_id() { |
314 | uint32_t game_time = get_game_time(); |
315 | uint32_t i; |
316 | for (i = 1; i < 7 && time_in_ms[i] <= game_time; i++) { |
317 | @@ -376,29 +379,47 @@ |
318 | needs_update_ = false; |
319 | } |
320 | |
321 | +int32_t WuiPlotArea::initialize_update() { |
322 | + // Initialize |
323 | + for (uint32_t i = 0; i < plotdata_.size(); ++i) { |
324 | + plotdata_[i].relative_data->clear(); |
325 | + } |
326 | + // Get range |
327 | + time_ms_ = get_plot_time(); |
328 | + if (plotmode_ == Plotmode::kAbsolute) { |
329 | + sub_ = |
330 | + (xline_length_ - kSpaceLeftOfLabel) |
331 | + / |
332 | + (static_cast<float>(time_ms_) |
333 | + / |
334 | + static_cast<float>(sample_rate_)); |
335 | + } else { |
336 | + sub_ = (xline_length_ - kSpaceLeftOfLabel) / static_cast<float>(KNoSamples); |
337 | + } |
338 | + |
339 | + // How many do we aggregate when relative plotting |
340 | + return calc_how_many(time_ms_, sample_rate_); |
341 | +} |
342 | |
343 | // Find the maximum value. |
344 | void WuiPlotArea::update() { |
345 | - float const xline_length = get_inner_w() - space_at_right - spacing; |
346 | - |
347 | - time_ms_ = get_plot_time(); |
348 | - |
349 | - // How many do we take together when relative ploting |
350 | - const int32_t how_many = calc_how_many(time_ms_, sample_rate_); |
351 | + const int32_t how_many = initialize_update(); |
352 | + |
353 | + // Calculate highest scale |
354 | highest_scale_ = 0; |
355 | - if (plotmode_ == PLOTMODE_ABSOLUTE) { |
356 | + if (plotmode_ == Plotmode::kAbsolute) { |
357 | for (uint32_t i = 0; i < plotdata_.size(); ++i) |
358 | if (plotdata_[i].showplot) { |
359 | - for (uint32_t l = 0; l < plotdata_[i].dataset->size(); ++l) |
360 | - if (highest_scale_ < (*plotdata_[i].dataset)[l]) |
361 | - highest_scale_ = (*plotdata_[i].dataset)[l]; |
362 | + for (uint32_t l = 0; l < plotdata_[i].absolute_data->size(); ++l) { |
363 | + if (highest_scale_ < (*plotdata_[i].absolute_data)[l]) { |
364 | + highest_scale_ = (*plotdata_[i].absolute_data)[l]; |
365 | + } |
366 | + } |
367 | } |
368 | } else { |
369 | for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) { |
370 | if (plotdata_[plot].showplot) { |
371 | - |
372 | - const std::vector<uint32_t> & dataset = *plotdata_[plot].dataset; |
373 | - |
374 | + const std::vector<uint32_t> & dataset = *plotdata_[plot].absolute_data; |
375 | uint32_t add = 0; |
376 | // Relative data, first entry is always zero. |
377 | for (uint32_t i = 0; i < dataset.size(); ++i) { |
378 | @@ -413,31 +434,21 @@ |
379 | } |
380 | } |
381 | |
382 | - // Update the datasets |
383 | - sub_ = |
384 | - (xline_length - space_left_of_label) |
385 | - / |
386 | - (static_cast<float>(time_ms_) |
387 | - / |
388 | - static_cast<float>(sample_rate_)); |
389 | - for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) { |
390 | - if (plotdata_[plot].showplot) { |
391 | - std::vector<uint32_t> const * dataset = plotdata_[plot].dataset; |
392 | - |
393 | - std::vector<uint32_t> data; |
394 | - if (plotmode_ == PLOTMODE_RELATIVE) { |
395 | + // Calculate plot data |
396 | + if (plotmode_ == Plotmode::kRelative) { |
397 | + for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) { |
398 | + if (plotdata_[plot].showplot) { |
399 | + std::vector<uint32_t> const * dataset = plotdata_[plot].absolute_data; |
400 | uint32_t add = 0; |
401 | // Relative data, first entry is always zero |
402 | - data.push_back(0); |
403 | + plotdata_[plot].relative_data->push_back(0); |
404 | for (uint32_t i = 0; i < dataset->size(); ++i) { |
405 | add += (*dataset)[i]; |
406 | if (0 == ((i + 1) % how_many)) { |
407 | - data.push_back(add); |
408 | + plotdata_[plot].relative_data->push_back(add); |
409 | add = 0; |
410 | } |
411 | } |
412 | - dataset = &data; |
413 | - sub_ = (xline_length - space_left_of_label) / static_cast<float>(nr_samples); |
414 | } |
415 | } |
416 | } |
417 | @@ -449,23 +460,26 @@ |
418 | * Draw this. This is the main function |
419 | */ |
420 | void WuiPlotArea::draw(RenderTarget & dst) { |
421 | - float const xline_length = get_inner_w() - space_at_right - spacing; |
422 | - float const yline_length = get_inner_h() - space_at_bottom - spacing; |
423 | + draw_plot(dst, get_inner_h() - kSpaceBottom, std::to_string(highest_scale_), highest_scale_); |
424 | +} |
425 | |
426 | - draw_diagram(time_ms_, get_inner_w(), get_inner_h(), xline_length, dst); |
427 | +void WuiPlotArea::draw_plot(RenderTarget& dst, float yoffset, const std::string& yscale_label, |
428 | + uint32_t highest_scale) { |
429 | + draw_diagram(time_ms_, get_inner_w(), get_inner_h(), xline_length_, dst); |
430 | |
431 | // print the maximal value into the top right corner |
432 | draw_value |
433 | - (std::to_string(highest_scale_), RGBColor(60, 125, 0), |
434 | - Point(get_inner_w() - space_at_right - 2, spacing + 2), dst); |
435 | + (yscale_label, RGBColor(60, 125, 0), |
436 | + Point(get_inner_w() - kSpaceRight - 2, kSpacing + 2), dst); |
437 | |
438 | // plot the pixels |
439 | for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) { |
440 | if (plotdata_[plot].showplot) { |
441 | - RGBColor color = plotdata_[plot].plotcolor; |
442 | - std::vector<uint32_t> const * dataset = plotdata_[plot].dataset; |
443 | draw_plot_line |
444 | - (dst, dataset, yline_length, highest_scale_, sub_, color, get_inner_h() - space_at_bottom); |
445 | + (dst, |
446 | + (plotmode_ == Plotmode::kRelative) ? |
447 | + plotdata_[plot].relative_data : plotdata_[plot].absolute_data, |
448 | + highest_scale, sub_, plotdata_[plot].plotcolor, yoffset); |
449 | } |
450 | } |
451 | } |
452 | @@ -476,34 +490,37 @@ |
453 | * \param sub horizontal difference between 2 y values |
454 | */ |
455 | void WuiPlotArea::draw_plot_line |
456 | - (RenderTarget & dst, std::vector<uint32_t> const * dataset, float const yline_length, |
457 | + (RenderTarget & dst, std::vector<uint32_t> const * dataset, |
458 | uint32_t const highest_scale, float const sub, RGBColor const color, int32_t const yoffset) |
459 | { |
460 | - float posx = get_inner_w() - space_at_right; |
461 | - const int lx = get_inner_w() - space_at_right; |
462 | - int ly = yoffset; |
463 | - // Init start point of the plot line with the first data value. |
464 | - // This prevents that the plot starts always at zero |
465 | - if (int value = (*dataset)[dataset->size() - 1]) { |
466 | - ly -= static_cast<int32_t>(scale_value(yline_length, highest_scale, value)); |
467 | - } |
468 | - |
469 | - std::vector<FloatPoint> points; |
470 | - points.emplace_back(lx, ly); |
471 | - |
472 | - for (int32_t i = dataset->size() - 1; i > 0 && posx > spacing; --i) { |
473 | - int32_t const curx = static_cast<int32_t>(posx); |
474 | - int32_t cury = yoffset; |
475 | - |
476 | - // Scale the line to the available space |
477 | - if (int32_t value = (*dataset)[i]) { |
478 | - const float length_y = scale_value(yline_length, highest_scale, value); |
479 | - cury -= static_cast<int32_t>(length_y); |
480 | - } |
481 | - points.emplace_back(curx, cury); |
482 | - posx -= sub; |
483 | - } |
484 | - dst.draw_line_strip(points, color, kPlotLinesWidth); |
485 | + if (!dataset->empty()) { |
486 | + float posx = get_inner_w() - kSpaceRight; |
487 | + const int lx = get_inner_w() - kSpaceRight; |
488 | + int ly = yoffset; |
489 | + // Init start point of the plot line with the first data value. |
490 | + // This prevents that the plot starts always at zero |
491 | + |
492 | + if (int value = (*dataset)[dataset->size() - 1]) { |
493 | + ly -= static_cast<int32_t>(scale_value(yline_length_, highest_scale, value)); |
494 | + } |
495 | + |
496 | + std::vector<FloatPoint> points; |
497 | + points.emplace_back(lx, ly); |
498 | + |
499 | + for (int32_t i = dataset->size() - 1; i > 0 && posx > kSpacing; --i) { |
500 | + int32_t const curx = static_cast<int32_t>(posx); |
501 | + int32_t cury = yoffset; |
502 | + |
503 | + // Scale the line to the available space |
504 | + if (int32_t value = (*dataset)[i]) { |
505 | + const float length_y = scale_value(yline_length_, highest_scale, value); |
506 | + cury -= static_cast<int32_t>(length_y); |
507 | + } |
508 | + points.emplace_back(curx, cury); |
509 | + posx -= sub; |
510 | + } |
511 | + dst.draw_line_strip(points, color, kPlotLinesWidth); |
512 | + } |
513 | } |
514 | |
515 | /* |
516 | @@ -517,7 +534,8 @@ |
517 | if (id >= plotdata_.size()) |
518 | plotdata_.resize(id + 1); |
519 | |
520 | - plotdata_[id].dataset = data; |
521 | + plotdata_[id].absolute_data = data; |
522 | + plotdata_[id].relative_data = new std::vector<uint32_t>(); // Will be filled in the update() function. |
523 | plotdata_[id].showplot = false; |
524 | plotdata_[id].plotcolor = color; |
525 | |
526 | @@ -544,17 +562,9 @@ |
527 | needs_update_ = true; |
528 | } |
529 | |
530 | -/* |
531 | - * Set sample rate the data uses |
532 | - */ |
533 | -void WuiPlotArea::set_sample_rate(uint32_t const id) { |
534 | - sample_rate_ = id; |
535 | - needs_update_ = true; |
536 | -} |
537 | - |
538 | |
539 | void WuiPlotAreaSlider::draw(RenderTarget & dst) { |
540 | - int32_t new_game_time_id = plot_area_.get_game_time_id(); |
541 | + uint32_t new_game_time_id = plot_area_.get_game_time_id(); |
542 | if (new_game_time_id != last_game_time_id_) { |
543 | last_game_time_id_ = new_game_time_id; |
544 | set_labels(plot_area_.get_labels()); |
545 | @@ -565,31 +575,27 @@ |
546 | |
547 | DifferentialPlotArea::DifferentialPlotArea |
548 | (UI::Panel * const parent, |
549 | - int32_t const x, int32_t const y, int32_t const w, int32_t const h) |
550 | + int32_t const x, int32_t const y, int32_t const w, int32_t const h, |
551 | + uint32_t sample_rate, Plotmode plotmode) |
552 | : |
553 | -WuiPlotArea (parent, x, y, w, h) |
554 | +WuiPlotArea (parent, x, y, w, h, sample_rate, plotmode) |
555 | { |
556 | update(); |
557 | } |
558 | |
559 | void DifferentialPlotArea::update() { |
560 | - float const xline_length = get_inner_w() - space_at_right - spacing; |
561 | - |
562 | - time_ms_ = get_plot_time(); |
563 | - |
564 | - // How many do we take together when relative ploting |
565 | - const int32_t how_many = calc_how_many(time_ms_, sample_rate_); |
566 | - |
567 | - // Find max and min value |
568 | + const int32_t how_many = initialize_update(); |
569 | + |
570 | + // Calculate highest scale |
571 | int32_t max = 0; |
572 | int32_t min = 0; |
573 | |
574 | - if (plotmode_ == PLOTMODE_ABSOLUTE) { |
575 | + if (plotmode_ == Plotmode::kAbsolute) { |
576 | for (uint32_t i = 0; i < plotdata_.size(); ++i) |
577 | if (plotdata_[i].showplot) { |
578 | - for (uint32_t l = 0; l < plotdata_[i].dataset->size(); ++l) { |
579 | - int32_t temp = (*plotdata_[i].dataset)[l] - |
580 | - (*negative_plotdata_[i].dataset)[l]; |
581 | + for (uint32_t l = 0; l < plotdata_[i].absolute_data->size(); ++l) { |
582 | + int32_t temp = (*plotdata_[i].absolute_data)[l] - |
583 | + (*negative_plotdata_[i].absolute_data)[l]; |
584 | if (max < temp) max = temp; |
585 | if (min > temp) min = temp; |
586 | } |
587 | @@ -598,8 +604,8 @@ |
588 | for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) |
589 | if (plotdata_[plot].showplot) { |
590 | |
591 | - const std::vector<uint32_t> & dataset = *plotdata_[plot].dataset; |
592 | - const std::vector<uint32_t> & ndataset = *negative_plotdata_[plot].dataset; |
593 | + const std::vector<uint32_t> & dataset = *plotdata_[plot].absolute_data; |
594 | + const std::vector<uint32_t> & ndataset = *negative_plotdata_[plot].absolute_data; |
595 | |
596 | int32_t add = 0; |
597 | // Relative data, first entry is always zero. |
598 | @@ -616,77 +622,43 @@ |
599 | |
600 | // Use equal positive and negative range |
601 | min = abs(min); |
602 | - highest_scale_ = 0; |
603 | - if (min > max) { |
604 | - highest_scale_ = min; |
605 | - } else { |
606 | - highest_scale_ = max; |
607 | - } |
608 | - |
609 | - // plot the pixels |
610 | - sub_ = |
611 | - xline_length |
612 | - / |
613 | - (static_cast<float>(time_ms_) |
614 | - / |
615 | - static_cast<float>(sample_rate_)); |
616 | - for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) |
617 | - if (plotdata_[plot].showplot) { |
618 | - std::vector<uint32_t> const * dataset = plotdata_[plot].dataset; |
619 | - std::vector<uint32_t> const * ndataset = negative_plotdata_[plot].dataset; |
620 | - |
621 | - std::vector<uint32_t> data_; |
622 | - if (plotmode_ == PLOTMODE_RELATIVE) { |
623 | - int32_t add = 0; |
624 | + highest_scale_ = (min > max) ? min : max; |
625 | + |
626 | + // Calculate plot data |
627 | + if (plotmode_ == Plotmode::kRelative) { |
628 | + for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) { |
629 | + if (plotdata_[plot].showplot) { |
630 | + std::vector<uint32_t> const * dataset = plotdata_[plot].absolute_data; |
631 | + std::vector<uint32_t> const * ndataset = negative_plotdata_[plot].absolute_data; |
632 | + uint32_t add = 0; |
633 | // Relative data, first entry is always zero |
634 | - data_.push_back(0); |
635 | + plotdata_[plot].relative_data->push_back(0); |
636 | for (uint32_t i = 0; i < dataset->size(); ++i) { |
637 | add += (*dataset)[i] - (*ndataset)[i]; |
638 | if (0 == ((i + 1) % how_many)) { |
639 | - data_.push_back(add); |
640 | + plotdata_[plot].relative_data->push_back(add); |
641 | add = 0; |
642 | } |
643 | } |
644 | - dataset = &data_; |
645 | - sub_ = xline_length / static_cast<float>(nr_samples); |
646 | } |
647 | } |
648 | + } |
649 | } |
650 | |
651 | void DifferentialPlotArea::draw(RenderTarget & dst) { |
652 | - float const xline_length = get_inner_w() - space_at_right - spacing; |
653 | - float const yline_length = get_inner_h() - space_at_bottom - spacing; |
654 | // yoffset of the zero line |
655 | - float const yoffset = spacing + ((get_inner_h() - space_at_bottom) - spacing) / 2; |
656 | + float const yoffset = kSpacing + ((get_inner_h() - kSpaceBottom) - kSpacing) / 2; |
657 | + draw_plot(dst, yoffset, std::to_string(highest_scale_), 2 * highest_scale_); |
658 | |
659 | - time_ms_ = get_plot_time(); |
660 | - draw_diagram(time_ms_, get_inner_w(), get_inner_h(), xline_length, dst); |
661 | + // Print the min value |
662 | + draw_value |
663 | + ((boost::format("-%u") % (highest_scale_)).str(), RGBColor(125, 0, 0), |
664 | + Point(get_inner_w() - kSpaceRight - 2, get_inner_h() - kSpacing - 15), dst); |
665 | |
666 | // draw zero line |
667 | - dst.draw_line_strip({FloatPoint(get_inner_w() - space_at_right, yoffset), |
668 | - FloatPoint(get_inner_w() - space_at_right - xline_length, yoffset)}, |
669 | + dst.draw_line_strip({FloatPoint(get_inner_w() - kSpaceRight, yoffset), |
670 | + FloatPoint(get_inner_w() - kSpaceRight - xline_length_, yoffset)}, |
671 | kZeroLineColor, kPlotLinesWidth); |
672 | - |
673 | - // Print the min and max values |
674 | - draw_value |
675 | - (std::to_string(highest_scale_), RGBColor(60, 125, 0), |
676 | - Point(get_inner_w() - space_at_right - 2, spacing + 2), dst); |
677 | - |
678 | - draw_value |
679 | - ((boost::format("-%u") % highest_scale_).str(), RGBColor(125, 0, 0), |
680 | - Point(get_inner_w() - space_at_right - 2, get_inner_h() - spacing - 15), dst); |
681 | - |
682 | - // plot the pixels |
683 | - for (uint32_t plot = 0; plot < plotdata_.size(); ++plot) |
684 | - if (plotdata_[plot].showplot) { |
685 | - |
686 | - RGBColor color = plotdata_[plot].plotcolor; |
687 | - std::vector<uint32_t> const * dataset = plotdata_[plot].dataset; |
688 | - |
689 | - // Highest_scale represent the space between zero line and top. |
690 | - // -> half of the whole differential plot area |
691 | - draw_plot_line(dst, dataset, yline_length, highest_scale_ * 2, sub_, color, yoffset); |
692 | - } |
693 | } |
694 | |
695 | /** |
696 | @@ -696,9 +668,10 @@ |
697 | void DifferentialPlotArea::register_negative_plot_data |
698 | (uint32_t const id, std::vector<uint32_t> const * const data) { |
699 | |
700 | - if (id >= negative_plotdata_.size()) |
701 | + if (id >= negative_plotdata_.size()) { |
702 | negative_plotdata_.resize(id + 1); |
703 | + } |
704 | |
705 | - negative_plotdata_[id].dataset = data; |
706 | + negative_plotdata_[id].absolute_data = data; |
707 | needs_update_ = true; |
708 | } |
709 | |
710 | === modified file 'src/wui/plot_area.h' |
711 | --- src/wui/plot_area.h 2016-03-19 17:30:37 +0000 |
712 | +++ src/wui/plot_area.h 2016-06-10 17:28:35 +0000 |
713 | @@ -44,16 +44,17 @@ |
714 | TIME_16_HOURS, |
715 | TIME_GAME, |
716 | }; |
717 | - enum PLOTMODE { |
718 | - // Always take the samples of some times together, so that the graph is |
719 | - // not completely zigg-zagged. |
720 | - PLOTMODE_RELATIVE, |
721 | - |
722 | - PLOTMODE_ABSOLUTE |
723 | + enum class Plotmode { |
724 | + // Always aggregate the samples of some time periods, so that the graph is |
725 | + // not completely zig-zagged. |
726 | + kRelative, |
727 | + kAbsolute |
728 | }; |
729 | |
730 | + // sample_rate is in in milliseconds |
731 | WuiPlotArea |
732 | - (UI::Panel * parent, int32_t x, int32_t y, int32_t w, int32_t h); |
733 | + (UI::Panel * parent, int32_t x, int32_t y, int32_t w, int32_t h, |
734 | + uint32_t sample_rate, Plotmode plotmode); |
735 | |
736 | /// Calls update() if needed |
737 | void think() override; |
738 | @@ -65,51 +66,54 @@ |
739 | needs_update_ = true; |
740 | } |
741 | |
742 | - void set_time_id(int32_t time) { |
743 | + void set_time_id(uint32_t time) { |
744 | if (time == game_time_id_) |
745 | set_time(TIME_GAME); |
746 | else |
747 | set_time(static_cast<TIME>(time)); |
748 | needs_update_ = true; |
749 | } |
750 | - TIME get_time() {return static_cast<TIME>(time_); } |
751 | - int32_t get_time_id() { |
752 | + TIME get_time() const {return static_cast<TIME>(time_); } |
753 | + int32_t get_time_id() const { |
754 | if (time_ == TIME_GAME) |
755 | return game_time_id_; |
756 | else |
757 | return time_; |
758 | } |
759 | - void set_sample_rate(uint32_t id); // in milliseconds |
760 | |
761 | - int32_t get_game_time_id(); |
762 | + uint32_t get_game_time_id(); |
763 | |
764 | void register_plot_data |
765 | (uint32_t id, const std::vector<uint32_t> * data, RGBColor); |
766 | void show_plot(uint32_t id, bool t); |
767 | |
768 | - void set_plotmode(int32_t id) {plotmode_ = id;} |
769 | - |
770 | void set_plotcolor(uint32_t id, RGBColor color); |
771 | |
772 | - std::vector<std::string> get_labels(); |
773 | + std::vector<std::string> get_labels() const; |
774 | |
775 | protected: |
776 | + void draw_plot(RenderTarget& dst, float yoffset, const std::string& yscale_label, |
777 | + uint32_t highest_scale); |
778 | void draw_plot_line |
779 | - (RenderTarget & dst, std::vector<uint32_t> const * dataset, float const yline_length, |
780 | + (RenderTarget & dst, std::vector<uint32_t> const * dataset, |
781 | uint32_t const highest_scale, float const sub, RGBColor const color, int32_t yoffset); |
782 | - uint32_t get_plot_time(); |
783 | + uint32_t get_plot_time() const; |
784 | /// Recalculates the data |
785 | virtual void update(); |
786 | + // Initializes relative_dataset and time scaling. |
787 | + // Returns how many values will be aggregated when relative plotting |
788 | + int32_t initialize_update(); |
789 | |
790 | struct PlotData { |
791 | - const std::vector<uint32_t> * dataset; |
792 | + const std::vector<uint32_t> * absolute_data; // The absolute dataset |
793 | + std::vector<uint32_t> * relative_data; // The relative dataset |
794 | bool showplot; |
795 | RGBColor plotcolor; |
796 | }; |
797 | std::vector<PlotData> plotdata_; |
798 | |
799 | - int32_t plotmode_; |
800 | - int32_t sample_rate_; |
801 | + Plotmode plotmode_; |
802 | + uint32_t sample_rate_; |
803 | |
804 | /// Whether there has ben a data update since the last time that think() was executed |
805 | bool needs_update_; |
806 | @@ -117,15 +121,17 @@ |
807 | uint32_t lastupdate_; |
808 | |
809 | /// For first updating and then plotting the data |
810 | + float const xline_length_; |
811 | + float const yline_length_; |
812 | uint32_t time_ms_; |
813 | uint32_t highest_scale_; |
814 | float sub_; |
815 | |
816 | private: |
817 | - uint32_t get_game_time(); |
818 | + uint32_t get_game_time() const; |
819 | |
820 | TIME time_; // How much do you want to list |
821 | - int32_t game_time_id_; // what label is used for TIME_GAME |
822 | + uint32_t game_time_id_; // what label is used for TIME_GAME |
823 | }; |
824 | |
825 | /** |
826 | @@ -161,7 +167,7 @@ |
827 | |
828 | private: |
829 | WuiPlotArea & plot_area_; |
830 | - int32_t last_game_time_id_; |
831 | + uint32_t last_game_time_id_; |
832 | }; |
833 | |
834 | /** |
835 | @@ -172,7 +178,8 @@ |
836 | struct DifferentialPlotArea : public WuiPlotArea { |
837 | public: |
838 | DifferentialPlotArea |
839 | - (UI::Panel * parent, int32_t x, int32_t y, int32_t w, int32_t h); |
840 | + (UI::Panel * parent, int32_t x, int32_t y, int32_t w, int32_t h, |
841 | + uint32_t sample_rate, Plotmode plotmode); |
842 | |
843 | void draw(RenderTarget &) override; |
844 | |
845 | @@ -184,13 +191,14 @@ |
846 | void update() override; |
847 | |
848 | private: |
849 | - |
850 | /** |
851 | - * for the negative plotdata only the values matter. The color and |
852 | + * For the negative plotdata, only the values matter. The color and |
853 | * visibility is determined by the normal plotdata. |
854 | + * We don't need relative data to fill - this is also done in the |
855 | + * normal plotdata |
856 | */ |
857 | struct ReducedPlotData { |
858 | - const std::vector<uint32_t> * dataset; |
859 | + const std::vector<uint32_t> * absolute_data; |
860 | }; |
861 | std::vector<ReducedPlotData> negative_plotdata_; |
862 | }; |
863 | |
864 | === modified file 'src/wui/ware_statistics_menu.cc' |
865 | --- src/wui/ware_statistics_menu.cc 2016-03-30 07:20:05 +0000 |
866 | +++ src/wui/ware_statistics_menu.cc 2016-06-10 17:28:35 +0000 |
867 | @@ -164,9 +164,9 @@ |
868 | plot_production_ = |
869 | new WuiPlotArea |
870 | (tabs, |
871 | - 0, 0, kPlotWidth, kPlotHeight + kSpacing); |
872 | - plot_production_->set_sample_rate(STATISTICS_SAMPLE_TIME); |
873 | - plot_production_->set_plotmode(WuiPlotArea::PLOTMODE_RELATIVE); |
874 | + 0, 0, kPlotWidth, kPlotHeight + kSpacing, |
875 | + kStatisticsSampleTime, |
876 | + WuiPlotArea::Plotmode::kRelative); |
877 | |
878 | tabs->add |
879 | ("production", g_gr->images().get(pic_tab_production), |
880 | @@ -175,9 +175,9 @@ |
881 | plot_consumption_ = |
882 | new WuiPlotArea |
883 | (tabs, |
884 | - 0, 0, kPlotWidth, kPlotHeight + kSpacing); |
885 | - plot_consumption_->set_sample_rate(STATISTICS_SAMPLE_TIME); |
886 | - plot_consumption_->set_plotmode(WuiPlotArea::PLOTMODE_RELATIVE); |
887 | + 0, 0, kPlotWidth, kPlotHeight + kSpacing, |
888 | + kStatisticsSampleTime, |
889 | + WuiPlotArea::Plotmode::kRelative); |
890 | |
891 | tabs->add |
892 | ("consumption", g_gr->images().get(pic_tab_consumption), |
893 | @@ -186,9 +186,9 @@ |
894 | plot_economy_ = |
895 | new DifferentialPlotArea |
896 | (tabs, |
897 | - 0, 0, kPlotWidth, kPlotHeight + kSpacing); |
898 | - plot_economy_->set_sample_rate(STATISTICS_SAMPLE_TIME); |
899 | - plot_economy_->set_plotmode(WuiPlotArea::PLOTMODE_RELATIVE); |
900 | + 0, 0, kPlotWidth, kPlotHeight + kSpacing, |
901 | + kStatisticsSampleTime, |
902 | + WuiPlotArea::Plotmode::kRelative); |
903 | |
904 | tabs->add |
905 | ("economy_health", g_gr->images().get(pic_tab_economy), |
906 | @@ -196,9 +196,9 @@ |
907 | |
908 | plot_stock_ = new WuiPlotArea |
909 | (tabs, |
910 | - 0, 0, kPlotWidth, kPlotHeight + kSpacing); |
911 | - plot_stock_->set_sample_rate(STATISTICS_SAMPLE_TIME); |
912 | - plot_stock_->set_plotmode(WuiPlotArea::PLOTMODE_ABSOLUTE); |
913 | + 0, 0, kPlotWidth, kPlotHeight + kSpacing, |
914 | + kStatisticsSampleTime, |
915 | + WuiPlotArea::Plotmode::kAbsolute); |
916 | |
917 | tabs->add |
918 | ("stock", g_gr->images().get(pic_tab_stock), |
Good savegames for testing are attached to bug https:/ /bugs.launchpad .net/widelands/ +bug/1573057