1 | // SuperTux (Statistics module) |
2 | // Copyright (C) 2004 Ricardo Cruz <rick2@aeiou.pt> |
3 | // Copyright (C) 2006 Ondrej Hosek <ondra.hosek@gmail.com> |
4 | // Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de> |
5 | // |
6 | // This program is free software: you can redistribute it and/or modify |
7 | // it under the terms of the GNU General Public License as published by |
8 | // the Free Software Foundation, either version 3 of the License, or |
9 | // (at your option) any later version. |
10 | // |
11 | // This program is distributed in the hope that it will be useful, |
12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | // GNU General Public License for more details. |
15 | // |
16 | // You should have received a copy of the GNU General Public License |
17 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | |
19 | #include "supertux/statistics.hpp" |
20 | |
21 | #include <algorithm> |
22 | #include <iomanip> |
23 | #include <limits> |
24 | |
25 | #include "math/util.hpp" |
26 | #include "squirrel/squirrel_util.hpp" |
27 | #include "supertux/globals.hpp" |
28 | #include "supertux/level.hpp" |
29 | #include "supertux/resources.hpp" |
30 | #include "util/gettext.hpp" |
31 | #include "util/log.hpp" |
32 | #include "video/drawing_context.hpp" |
33 | #include "video/surface.hpp" |
34 | #include "video/video_system.hpp" |
35 | #include "video/viewport.hpp" |
36 | |
37 | Statistics::Statistics() : |
38 | m_status(INVALID), |
39 | m_total_coins(), |
40 | m_total_badguys(), |
41 | m_total_secrets(), |
42 | m_coins(), |
43 | m_badguys(), |
44 | m_secrets(), |
45 | m_time(), |
46 | m_max_width(256), |
47 | CAPTION_MAX_COINS(_("Max coins collected:" )), |
48 | CAPTION_MAX_FRAGGING(_("Max fragging:" )), |
49 | CAPTION_MAX_SECRETS(_("Max secrets found:" )), |
50 | CAPTION_BEST_TIME(_("Best time completed:" )), |
51 | CAPTION_TARGET_TIME(_("Level target time:" )), |
52 | WMAP_INFO_LEFT_X(), |
53 | WMAP_INFO_RIGHT_X(), |
54 | WMAP_INFO_TOP_Y1(), |
55 | WMAP_INFO_TOP_Y2() |
56 | { |
57 | calculate_max_caption_length(); |
58 | WMAP_INFO_LEFT_X = static_cast<float>(SCREEN_WIDTH) - 32.0f - static_cast<float>(m_max_width); |
59 | WMAP_INFO_RIGHT_X = WMAP_INFO_LEFT_X + static_cast<float>(m_max_width); |
60 | WMAP_INFO_TOP_Y1 = static_cast<float>(SCREEN_HEIGHT) - 100.0f; |
61 | WMAP_INFO_TOP_Y2 = WMAP_INFO_TOP_Y1 + 16.0f; |
62 | } |
63 | |
64 | void |
65 | Statistics::calculate_max_caption_length() |
66 | { |
67 | auto captions = {CAPTION_MAX_COINS, CAPTION_MAX_FRAGGING, CAPTION_MAX_SECRETS, |
68 | CAPTION_BEST_TIME, CAPTION_TARGET_TIME}; |
69 | |
70 | m_max_width = 256; |
71 | |
72 | for (const auto& caption : captions) |
73 | { |
74 | auto font = Resources::small_font; |
75 | // Add padding the size of lengthiest string: |
76 | auto width = font->get_text_width(caption) + |
77 | font->get_text_width("XX:XX:XX" ); |
78 | if (width >= static_cast<float>(m_max_width)) |
79 | { |
80 | m_max_width = static_cast<int>(width); |
81 | } |
82 | } |
83 | } |
84 | |
85 | void |
86 | Statistics::serialize_to_squirrel(SquirrelVM& vm) const |
87 | { |
88 | if (m_status != FINAL) return; |
89 | |
90 | vm.begin_table("statistics" ); |
91 | vm.store_int("coins-collected" , m_coins); |
92 | vm.store_int("badguys-killed" , m_badguys); |
93 | vm.store_int("secrets-found" , m_secrets); |
94 | vm.store_float("time-needed" , m_time); |
95 | vm.store_int("coins-collected-total" , m_total_coins); |
96 | vm.store_int("badguys-killed-total" , m_total_badguys); |
97 | vm.store_int("secrets-found-total" , m_total_secrets); |
98 | vm.end_table("statistics" ); |
99 | } |
100 | |
101 | void |
102 | Statistics::unserialize_from_squirrel(SquirrelVM& vm) |
103 | { |
104 | try |
105 | { |
106 | vm.get_table_entry("statistics" ); |
107 | vm.get_int("coins-collected" , m_coins); |
108 | vm.get_int("badguys-killed" , m_badguys); |
109 | vm.get_int("secrets-found" , m_secrets); |
110 | vm.get_float("time-needed" , m_time); |
111 | vm.get_int("coins-collected-total" , m_total_coins); |
112 | vm.get_int("badguys-killed-total" , m_total_badguys); |
113 | vm.get_int("secrets-found-total" , m_total_secrets); |
114 | sq_pop(vm.get_vm(), 1); |
115 | |
116 | m_status = FINAL; |
117 | } |
118 | catch(const std::exception&) |
119 | { |
120 | // ignore non-existing or malformed statistics table |
121 | } |
122 | } |
123 | |
124 | void |
125 | Statistics::draw_worldmap_info(DrawingContext& context, float target_time) |
126 | { |
127 | if (m_status != FINAL) return; |
128 | |
129 | // check to see if screen size has been changed |
130 | if (!(WMAP_INFO_TOP_Y1 == static_cast<float>(SCREEN_HEIGHT - 100))) { |
131 | calculate_max_caption_length(); |
132 | WMAP_INFO_LEFT_X = static_cast<float>(context.get_width() - 32 - m_max_width); |
133 | WMAP_INFO_RIGHT_X = WMAP_INFO_LEFT_X + static_cast<float>(m_max_width); |
134 | WMAP_INFO_TOP_Y1 = static_cast<float>(SCREEN_HEIGHT - 100); |
135 | WMAP_INFO_TOP_Y2 = WMAP_INFO_TOP_Y1 + 16; |
136 | } |
137 | |
138 | context.color().draw_text( |
139 | Resources::small_font, std::string("- " ) + _("Best Level Statistics" ) + " -" , |
140 | Vector((WMAP_INFO_LEFT_X + WMAP_INFO_RIGHT_X) / 2, WMAP_INFO_TOP_Y1), |
141 | ALIGN_CENTER, LAYER_HUD,Statistics::header_color); |
142 | |
143 | std::string caption_buf; |
144 | std::string stat_buf; |
145 | float posy = WMAP_INFO_TOP_Y2; |
146 | |
147 | for (int stat_no = 0; stat_no < 5; stat_no++) { |
148 | switch (stat_no) |
149 | { |
150 | case 0: |
151 | caption_buf = CAPTION_MAX_COINS; |
152 | stat_buf = coins_to_string(m_coins, m_total_coins); |
153 | break; |
154 | case 1: |
155 | caption_buf = CAPTION_MAX_FRAGGING; |
156 | stat_buf = frags_to_string(m_badguys, m_total_badguys); |
157 | break; |
158 | case 2: |
159 | caption_buf = CAPTION_MAX_SECRETS; |
160 | stat_buf = secrets_to_string(m_secrets, m_total_secrets); |
161 | break; |
162 | case 3: |
163 | caption_buf = CAPTION_BEST_TIME; |
164 | stat_buf = time_to_string(m_time); |
165 | break; |
166 | case 4: |
167 | if (target_time != 0.0f) { // display target time only if defined for level |
168 | caption_buf = CAPTION_TARGET_TIME; |
169 | stat_buf = time_to_string(target_time); |
170 | } else { |
171 | caption_buf = "" ; |
172 | stat_buf = "" ; |
173 | } |
174 | break; |
175 | default: |
176 | log_debug << "Invalid stat requested to be drawn" << std::endl; |
177 | break; |
178 | } |
179 | |
180 | context.color().draw_text(Resources::small_font, caption_buf, Vector(WMAP_INFO_LEFT_X, posy), ALIGN_LEFT, LAYER_HUD, Statistics::header_color); |
181 | context.color().draw_text(Resources::small_font, stat_buf, Vector(WMAP_INFO_RIGHT_X, posy), ALIGN_RIGHT, LAYER_HUD, Statistics::header_color); |
182 | posy += Resources::small_font->get_height() + 2; |
183 | } |
184 | } |
185 | |
186 | void |
187 | Statistics::draw_endseq_panel(DrawingContext& context, Statistics* best_stats, const SurfacePtr& backdrop) |
188 | { |
189 | if (m_status != FINAL) return; |
190 | |
191 | int box_w = 220+110+110; |
192 | int box_h = 30+20+20+20; |
193 | int box_x = static_cast<int>((context.get_width() - box_w) / 2); |
194 | int box_y = static_cast<int>(SCREEN_HEIGHT / 2) - box_h; |
195 | |
196 | int bd_w = static_cast<int>(backdrop->get_width()); |
197 | int bd_h = static_cast<int>(backdrop->get_height()); |
198 | int bd_x = static_cast<int>((context.get_width() - bd_w) / 2); |
199 | int bd_y = box_y + (box_h / 2) - (bd_h / 2); |
200 | |
201 | float col1_x = static_cast<float>(box_x); |
202 | float col2_x = col1_x + 200.0f; |
203 | float col3_x = col2_x + 130.0f; |
204 | |
205 | float row1_y = static_cast<float>(box_y); |
206 | float row2_y = row1_y + 30.0f; |
207 | float row3_y = row2_y + 20.0f; |
208 | float row4_y = row3_y + 20.0f; |
209 | float row5_y = row4_y + 20.0f; |
210 | |
211 | context.push_transform(); |
212 | context.set_alpha(0.5f); |
213 | context.color().draw_surface(backdrop, Vector(static_cast<float>(bd_x), static_cast<float>(bd_y)), LAYER_HUD); |
214 | context.pop_transform(); |
215 | |
216 | context.color().draw_text(Resources::normal_font, _("You" ), Vector(col2_x, row1_y), ALIGN_LEFT, LAYER_HUD, Statistics::header_color); |
217 | if (best_stats) |
218 | context.color().draw_text(Resources::normal_font, _("Best" ), Vector(col3_x, row1_y), ALIGN_LEFT, LAYER_HUD, Statistics::header_color); |
219 | |
220 | context.color().draw_text(Resources::normal_font, _("Coins" ), Vector(col2_x - 16.0f, static_cast<float>(row3_y)), ALIGN_RIGHT, LAYER_HUD, Statistics::header_color); |
221 | context.color().draw_text(Resources::normal_font, coins_to_string(m_coins, m_total_coins), Vector(col2_x, static_cast<float>(row3_y)), ALIGN_LEFT, LAYER_HUD, Statistics::text_color); |
222 | |
223 | if (best_stats) { |
224 | int coins_best = (best_stats->m_coins > m_coins) ? best_stats->m_coins : m_coins; |
225 | int total_coins_best = (best_stats->m_total_coins > m_total_coins) ? best_stats->m_total_coins : m_total_coins; |
226 | context.color().draw_text(Resources::normal_font, coins_to_string(coins_best, total_coins_best), Vector(col3_x, static_cast<float>(row3_y)), ALIGN_LEFT, LAYER_HUD, Statistics::text_color); |
227 | } |
228 | |
229 | context.color().draw_text(Resources::normal_font, _("Badguys" ), Vector(col2_x - 16.0f, static_cast<float>(row4_y)), ALIGN_RIGHT, LAYER_HUD, Statistics::header_color); |
230 | context.color().draw_text(Resources::normal_font, frags_to_string(m_badguys, m_total_badguys), Vector(col2_x, static_cast<float>(row4_y)), ALIGN_LEFT, LAYER_HUD, Statistics::text_color); |
231 | if (best_stats) { |
232 | int badguys_best = (best_stats->m_badguys > m_badguys) ? best_stats->m_badguys : m_badguys; |
233 | int total_badguys_best = (best_stats->m_total_badguys > m_total_badguys) ? best_stats->m_total_badguys : m_total_badguys; |
234 | context.color().draw_text(Resources::normal_font, frags_to_string(badguys_best, total_badguys_best), Vector(col3_x, row4_y), ALIGN_LEFT, LAYER_HUD, Statistics::text_color); |
235 | } |
236 | |
237 | context.color().draw_text(Resources::normal_font, _("Secrets" ), Vector(col2_x-16, row5_y), ALIGN_RIGHT, LAYER_HUD, Statistics::header_color); |
238 | context.color().draw_text(Resources::normal_font, secrets_to_string(m_secrets, m_total_secrets), Vector(col2_x, row5_y), ALIGN_LEFT, LAYER_HUD, Statistics::text_color); |
239 | if (best_stats) { |
240 | int secrets_best = (best_stats->m_secrets > m_secrets) ? best_stats->m_secrets : m_secrets; |
241 | int total_secrets_best = (best_stats->m_total_secrets > m_total_secrets) ? best_stats->m_total_secrets : m_total_secrets; |
242 | context.color().draw_text(Resources::normal_font, secrets_to_string(secrets_best, total_secrets_best), Vector(col3_x, row5_y), ALIGN_LEFT, LAYER_HUD, Statistics::text_color); |
243 | } |
244 | |
245 | context.color().draw_text(Resources::normal_font, _("Time" ), Vector(col2_x - 16, row2_y), ALIGN_RIGHT, LAYER_HUD, Statistics::header_color); |
246 | context.color().draw_text(Resources::normal_font, time_to_string(m_time), Vector(col2_x, row2_y), ALIGN_LEFT, LAYER_HUD, Statistics::text_color); |
247 | if (best_stats) { |
248 | float time_best = (best_stats->m_time < m_time && best_stats->m_time > 0.0f) ? best_stats->m_time : m_time; |
249 | context.color().draw_text(Resources::normal_font, time_to_string(time_best), Vector(col3_x, row2_y), ALIGN_LEFT, LAYER_HUD, Statistics::text_color); |
250 | } |
251 | } |
252 | |
253 | void |
254 | Statistics::init(const Level& level) |
255 | { |
256 | m_status = ACCUMULATING; |
257 | |
258 | m_coins = 0; |
259 | m_badguys = 0; |
260 | m_secrets = 0; |
261 | |
262 | m_total_coins = level.get_total_coins(); |
263 | m_total_badguys = level.get_total_badguys(); |
264 | m_total_secrets = level.get_total_secrets(); |
265 | } |
266 | |
267 | void |
268 | Statistics::finish(float time) |
269 | { |
270 | m_status = FINAL; |
271 | m_time = time; |
272 | } |
273 | |
274 | void |
275 | Statistics::invalidate() |
276 | { |
277 | m_status = INVALID; |
278 | } |
279 | |
280 | void |
281 | Statistics::update(const Statistics& other) |
282 | { |
283 | if (other.m_status != FINAL) return; |
284 | |
285 | m_coins = std::max(m_coins, other.m_coins); |
286 | m_badguys = std::max(m_badguys, other.m_badguys); |
287 | m_secrets = std::max(m_secrets, other.m_secrets); |
288 | if (m_time == 0) |
289 | m_time = other.m_time; |
290 | else |
291 | m_time = std::min(m_time, other.m_time); |
292 | |
293 | m_total_coins = other.m_total_coins; |
294 | m_total_badguys = other.m_total_badguys; |
295 | m_total_secrets = other.m_total_secrets; |
296 | |
297 | m_coins = math::clamp(m_coins, 0, m_total_coins); |
298 | m_badguys = math::clamp(m_badguys, 0, m_total_badguys); |
299 | m_secrets = math::clamp(m_secrets, 0, m_total_secrets); |
300 | m_status = FINAL; |
301 | } |
302 | |
303 | bool |
304 | Statistics::completed(const Statistics& stats, const float target_time) const |
305 | { |
306 | return (stats.m_coins == stats.m_total_coins && |
307 | stats.m_badguys == stats.m_total_badguys && |
308 | stats.m_secrets == stats.m_total_secrets && |
309 | ((target_time == 0.0f) || (stats.m_time <= target_time))); |
310 | } |
311 | |
312 | std::string |
313 | Statistics::coins_to_string(int coins, int total_coins) |
314 | { |
315 | std::ostringstream os; |
316 | os << std::min(std::min(coins, total_coins), 999) << "/" << std::min(total_coins, 999); |
317 | return os.str(); |
318 | } |
319 | |
320 | std::string |
321 | Statistics::frags_to_string(int badguys, int total_badguys) |
322 | { |
323 | std::ostringstream os; |
324 | os << std::min(std::min(badguys, total_badguys), 999) << "/" << std::min(total_badguys, 999); |
325 | return os.str(); |
326 | } |
327 | |
328 | std::string |
329 | Statistics::time_to_string(float time) |
330 | { |
331 | int time_csecs = static_cast<int>(time * 100); |
332 | int mins = (time_csecs / 6000); |
333 | int secs = (time_csecs % 6000) / 100; |
334 | int cscs = (time_csecs % 6000) % 100; |
335 | |
336 | std::ostringstream os; |
337 | if (time == 0.0f) |
338 | { |
339 | os << "--:--:--" ; |
340 | } |
341 | else |
342 | { |
343 | os << std::setw(2) << std::setfill('0') << mins << ":" << std::setw(2) << std::setfill('0') << secs << "." << std::setw(2) << std::setfill('0') << cscs; |
344 | } |
345 | |
346 | return os.str(); |
347 | } |
348 | |
349 | std::string |
350 | Statistics::secrets_to_string(int secrets, int total_secrets) |
351 | { |
352 | std::ostringstream os; |
353 | os << std::min(secrets, 999) << "/" << std::min(total_secrets, 999); |
354 | return os.str(); |
355 | } |
356 | |
357 | /* EOF */ |
358 | |