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
37Statistics::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
64void
65Statistics::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
85void
86Statistics::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
101void
102Statistics::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
124void
125Statistics::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
186void
187Statistics::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
253void
254Statistics::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
267void
268Statistics::finish(float time)
269{
270 m_status = FINAL;
271 m_time = time;
272}
273
274void
275Statistics::invalidate()
276{
277 m_status = INVALID;
278}
279
280void
281Statistics::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
303bool
304Statistics::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
312std::string
313Statistics::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
320std::string
321Statistics::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
328std::string
329Statistics::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
349std::string
350Statistics::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