1 //
2 // SuperTuxKart - a fun racing game with go-kart
3 // Copyright (C) 2004-2015 Steve Baker <sjbaker1@airmail.net>
4 // Copyright (C) 2006-2015 Joerg Henrichs, SuperTuxKart-Team, Steve Baker
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 3
9 // of the License, or (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, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 #include "states_screens/race_gui.hpp"
21
22 using namespace irr;
23
24 #include <algorithm>
25 #include <limits>
26
27 #include "challenges/story_mode_timer.hpp"
28 #include "challenges/unlock_manager.hpp"
29 #include "config/user_config.hpp"
30 #include "font/font_drawer.hpp"
31 #include "graphics/camera.hpp"
32 #include "graphics/2dutils.hpp"
33 #ifndef SERVER_ONLY
34 #include "graphics/glwrap.hpp"
35 #endif
36 #include "graphics/irr_driver.hpp"
37 #include "graphics/material.hpp"
38 #include "graphics/material_manager.hpp"
39 #include "guiengine/engine.hpp"
40 #include "guiengine/modaldialog.hpp"
41 #include "guiengine/scalable_font.hpp"
42 #include "io/file_manager.hpp"
43 #include "items/powerup_manager.hpp"
44 #include "items/projectile_manager.hpp"
45 #include "karts/abstract_kart.hpp"
46 #include "karts/controller/controller.hpp"
47 #include "karts/controller/spare_tire_ai.hpp"
48 #include "karts/kart_properties.hpp"
49 #include "karts/kart_properties_manager.hpp"
50 #include "modes/capture_the_flag.hpp"
51 #include "modes/follow_the_leader.hpp"
52 #include "modes/linear_world.hpp"
53 #include "modes/world.hpp"
54 #include "modes/soccer_world.hpp"
55 #include "network/protocols/client_lobby.hpp"
56 #include "race/race_manager.hpp"
57 #include "states_screens/race_gui_multitouch.hpp"
58 #include "tracks/track.hpp"
59 #include "tracks/track_object_manager.hpp"
60 #include "utils/constants.hpp"
61 #include "utils/string_utils.hpp"
62 #include "utils/translation.hpp"
63
64 /** The constructor is called before anything is attached to the scene node.
65 * So rendering to a texture can be done here. But world is not yet fully
66 * created, so only the race manager can be accessed safely.
67 */
RaceGUI()68 RaceGUI::RaceGUI()
69 {
70 m_enabled = true;
71
72 if (UserConfigParams::m_artist_debug_mode && UserConfigParams::m_hide_gui)
73 m_enabled = false;
74
75 initSize();
76 bool multitouch_enabled = (UserConfigParams::m_multitouch_active == 1 &&
77 irr_driver->getDevice()->supportsTouchDevice()) ||
78 UserConfigParams::m_multitouch_active > 1;
79
80 if (multitouch_enabled && UserConfigParams::m_multitouch_draw_gui &&
81 RaceManager::get()->getNumLocalPlayers() == 1)
82 {
83 m_multitouch_gui = new RaceGUIMultitouch(this);
84 }
85
86 calculateMinimapSize();
87
88 m_is_tutorial = (RaceManager::get()->getTrackName() == "tutorial");
89
90 // Load speedmeter texture before rendering the first frame
91 m_speed_meter_icon = material_manager->getMaterial("speedback.png");
92 m_speed_meter_icon->getTexture(false,false);
93 m_speed_bar_icon = material_manager->getMaterial("speedfore.png");
94 m_speed_bar_icon->getTexture(false,false);
95 //createMarkerTexture();
96
97 // Load icon textures for later reuse
98 m_red_team = irr_driver->getTexture(FileManager::GUI_ICON, "soccer_ball_red.png");
99 m_blue_team = irr_driver->getTexture(FileManager::GUI_ICON, "soccer_ball_blue.png");
100 m_red_flag = irr_driver->getTexture(FileManager::GUI_ICON, "red_flag.png");
101 m_blue_flag = irr_driver->getTexture(FileManager::GUI_ICON, "blue_flag.png");
102 m_soccer_ball = irr_driver->getTexture(FileManager::GUI_ICON, "soccer_ball_normal.png");
103 m_heart_icon = irr_driver->getTexture(FileManager::GUI_ICON, "heart.png");
104 m_basket_ball_icon = irr_driver->getTexture(FileManager::GUI_ICON, "rubber_ball-icon.png");
105 m_champion = irr_driver->getTexture(FileManager::GUI_ICON, "cup_gold.png");
106 } // RaceGUI
107
108 // ----------------------------------------------------------------------------
109 /** Called when loading the race gui or screen resized. */
initSize()110 void RaceGUI::initSize()
111 {
112 RaceGUIBase::initSize();
113 // Determine maximum length of the rank/lap text, in order to
114 // align those texts properly on the right side of the viewport.
115 gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
116 core::dimension2du area = font->getDimension(L"99:99.999");
117 m_timer_width = area.Width;
118 m_font_height = area.Height;
119
120 area = font->getDimension(L"99.999");
121 m_small_precise_timer_width = area.Width;
122
123 area = font->getDimension(L"99:99.999");
124 m_big_precise_timer_width = area.Width;
125
126 area = font->getDimension(L"-");
127 m_negative_timer_additional_width = area.Width;
128
129 if (RaceManager::get()->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER ||
130 RaceManager::get()->isBattleMode() ||
131 RaceManager::get()->getNumLaps() > 9)
132 m_lap_width = font->getDimension(L"99/99").Width;
133 else
134 m_lap_width = font->getDimension(L"9/9").Width;
135 } // initSize
136
137 //-----------------------------------------------------------------------------
~RaceGUI()138 RaceGUI::~RaceGUI()
139 {
140 delete m_multitouch_gui;
141 } // ~Racegui
142
143
144 //-----------------------------------------------------------------------------
init()145 void RaceGUI::init()
146 {
147 RaceGUIBase::init();
148 // Technically we only need getNumLocalPlayers, but using the
149 // global kart id to find the data for a specific kart.
150 int n = RaceManager::get()->getNumberOfKarts();
151
152 m_animation_states.resize(n);
153 m_rank_animation_duration.resize(n);
154 m_last_ranks.resize(n);
155 } // init
156
157 //-----------------------------------------------------------------------------
158 /** Reset the gui before a race. It initialised all rank animation related
159 * values back to the default.
160 */
reset()161 void RaceGUI::reset()
162 {
163 RaceGUIBase::reset();
164 for(unsigned int i=0; i<RaceManager::get()->getNumberOfKarts(); i++)
165 {
166 m_animation_states[i] = AS_NONE;
167 m_last_ranks[i] = i+1;
168 }
169 } // reset
170
171 //-----------------------------------------------------------------------------
calculateMinimapSize()172 void RaceGUI::calculateMinimapSize()
173 {
174 float map_size_splitscreen = 1.0f;
175
176 // If there are four players or more in splitscreen
177 // and the map is in a player view, scale down the map
178 if (RaceManager::get()->getNumLocalPlayers() >= 4 && !RaceManager::get()->getIfEmptyScreenSpaceExists())
179 {
180 // If the resolution is wider than 4:3, we don't have to scaledown the minimap as much
181 // Uses some margin, in case the game's screen is not exactly 4:3
182 if ( ((float) irr_driver->getFrameSize().Width / (float) irr_driver->getFrameSize().Height) >
183 (4.1f/3.0f))
184 {
185 if (RaceManager::get()->getNumLocalPlayers() == 4)
186 map_size_splitscreen = 0.75f;
187 else
188 map_size_splitscreen = 0.5f;
189 }
190 else
191 map_size_splitscreen = 0.5f;
192 }
193
194 // Originally m_map_height was 100, and we take 480 as minimum res
195 float scaling = std::min(irr_driver->getFrameSize().Height,
196 irr_driver->getFrameSize().Width) / 480.0f;
197 const float map_size = stk_config->m_minimap_size * map_size_splitscreen;
198 const float top_margin = 3.5f * m_font_height;
199
200 // Check if we have enough space for minimap when touch steering is enabled
201 if (m_multitouch_gui != NULL && !m_multitouch_gui->isSpectatorMode())
202 {
203 const float map_bottom = (float)(irr_driver->getActualScreenSize().Height -
204 m_multitouch_gui->getHeight());
205
206 if ((map_size + 20.0f) * scaling > map_bottom - top_margin)
207 {
208 scaling = (map_bottom - top_margin) / (map_size + 20.0f);
209 }
210
211 // Use some reasonable minimum scale, because minimap size can be
212 // changed during the race
213 scaling = std::max(scaling,
214 irr_driver->getActualScreenSize().Height * 0.15f /
215 (map_size + 20.0f));
216 }
217
218 // Marker texture has to be power-of-two for (old) OpenGL compliance
219 //m_marker_rendered_size = 2 << ((int) ceil(1.0 + log(32.0 * scaling)));
220 m_minimap_ai_size = (int)( stk_config->m_minimap_ai_icon * scaling);
221 m_minimap_player_size = (int)( stk_config->m_minimap_player_icon * scaling);
222 m_map_width = (int)(map_size * scaling);
223 m_map_height = (int)(map_size * scaling);
224
225 if ((UserConfigParams::m_minimap_display == 1 && /*map on the right side*/
226 RaceManager::get()->getNumLocalPlayers() == 1) || m_multitouch_gui)
227 {
228 m_map_left = (int)(irr_driver->getActualScreenSize().Width -
229 m_map_width - 10.0f*scaling);
230 m_map_bottom = (int)(3*irr_driver->getActualScreenSize().Height/4 -
231 m_map_height);
232 }
233 else if ((UserConfigParams::m_minimap_display == 3 && /*map on the center of the screen*/
234 RaceManager::get()->getNumLocalPlayers() == 1) || m_multitouch_gui)
235 {
236 m_map_left = (int)(irr_driver->getActualScreenSize().Width / 2);
237 if (m_map_left + m_map_width > (int)irr_driver->getActualScreenSize().Width)
238 m_map_left = (int)(irr_driver->getActualScreenSize().Width - m_map_width);
239 m_map_bottom = (int)( 10.0f * scaling);
240 }
241 else // default, map in the bottom-left corner
242 {
243 m_map_left = (int)( 10.0f * scaling);
244 m_map_bottom = (int)( 10.0f * scaling);
245 }
246
247 // Minimap is also rendered bigger via OpenGL, so find power-of-two again
248 const int map_texture = 2 << ((int) ceil(1.0 + log(128.0 * scaling)));
249 m_map_rendered_width = map_texture;
250 m_map_rendered_height = map_texture;
251
252
253 // special case : when 3 players play, use available 4th space for such things
254 if (RaceManager::get()->getIfEmptyScreenSpaceExists())
255 {
256 m_map_left = irr_driver->getActualScreenSize().Width -
257 m_map_width - (int)( 10.0f * scaling);
258 m_map_bottom = (int)( 10.0f * scaling);
259 }
260 else if (m_multitouch_gui != NULL && !m_multitouch_gui->isSpectatorMode())
261 {
262 m_map_left = (int)((irr_driver->getActualScreenSize().Width -
263 m_map_width) * 0.95f);
264 m_map_bottom = (int)(irr_driver->getActualScreenSize().Height -
265 top_margin - m_map_height);
266 }
267 } // calculateMinimapSize
268
269 //-----------------------------------------------------------------------------
270 /** Render all global parts of the race gui, i.e. things that are only
271 * displayed once even in splitscreen.
272 * \param dt Timestep sized.
273 */
renderGlobal(float dt)274 void RaceGUI::renderGlobal(float dt)
275 {
276 #ifndef SERVER_ONLY
277 RaceGUIBase::renderGlobal(dt);
278 cleanupMessages(dt);
279
280 // Special case : when 3 players play, use 4th window to display such
281 // stuff (but we must clear it)
282 if (RaceManager::get()->getIfEmptyScreenSpaceExists() &&
283 !GUIEngine::ModalDialog::isADialogActive())
284 {
285 static video::SColor black = video::SColor(255,0,0,0);
286
287 GL32_draw2DRectangle(black, irr_driver->getSplitscreenWindow(
288 RaceManager::get()->getNumLocalPlayers()));
289 }
290
291 World *world = World::getWorld();
292 assert(world != NULL);
293 if(world->getPhase() >= WorldStatus::WAIT_FOR_SERVER_PHASE &&
294 world->getPhase() <= WorldStatus::GO_PHASE )
295 {
296 drawGlobalReadySetGo();
297 }
298 else if (world->isGoalPhase())
299 drawGlobalGoal();
300
301 if (!m_enabled) return;
302
303 // Display the story mode timer if not in speedrun mode
304 // If in speedrun mode, it is taken care of in GUI engine
305 // as it must be displayed in all the game's screens
306 if (UserConfigParams::m_display_story_mode_timer &&
307 !UserConfigParams::m_speedrun_mode &&
308 RaceManager::get()->raceWasStartedFromOverworld())
309 irr_driver->displayStoryModeTimer();
310
311 // MiniMap is drawn when the players wait for the start countdown to end
312 drawGlobalMiniMap();
313
314 // Timer etc. are not displayed unless the game is actually started.
315 if(!world->isRacePhase()) return;
316
317 //drawGlobalTimer checks if it should display in the current phase/mode
318 FontDrawer::startBatching();
319 drawGlobalTimer();
320
321 if (!m_is_tutorial)
322 {
323 if (RaceManager::get()->isLinearRaceMode() &&
324 RaceManager::get()->hasGhostKarts() &&
325 RaceManager::get()->getNumberOfKarts() >= 2 )
326 drawLiveDifference();
327
328 if(world->getPhase() == WorldStatus::GO_PHASE ||
329 world->getPhase() == WorldStatus::MUSIC_PHASE)
330 {
331 drawGlobalMusicDescription();
332 }
333 }
334
335 if (!m_is_tutorial)
336 {
337 if (m_multitouch_gui != NULL)
338 {
339 drawGlobalPlayerIcons(m_multitouch_gui->getHeight());
340 }
341 else if (UserConfigParams::m_minimap_display == 0 || /*map in the bottom-left*/
342 (UserConfigParams::m_minimap_display == 1 &&
343 RaceManager::get()->getNumLocalPlayers() >= 2))
344 {
345 drawGlobalPlayerIcons(m_map_height + m_map_bottom);
346 }
347 else // map hidden or on the right side
348 {
349 drawGlobalPlayerIcons(0);
350 }
351 }
352 FontDrawer::endBatching();
353 #endif
354 } // renderGlobal
355
356 //-----------------------------------------------------------------------------
357 /** Render the details for a single player, i.e. speed, energy,
358 * collectibles, ...
359 * \param kart Pointer to the kart for which to render the view.
360 */
renderPlayerView(const Camera * camera,float dt)361 void RaceGUI::renderPlayerView(const Camera *camera, float dt)
362 {
363 #ifndef SERVER_ONLY
364 if (!m_enabled) return;
365
366 RaceGUIBase::renderPlayerView(camera, dt);
367
368 const core::recti &viewport = camera->getViewport();
369
370 core::vector2df scaling = camera->getScaling();
371 const AbstractKart *kart = camera->getKart();
372 if(!kart) return;
373
374 bool isSpectatorCam = Camera::getActiveCamera()->isSpectatorMode();
375
376 if (!isSpectatorCam) drawPlungerInFace(camera, dt);
377
378 if (viewport.getWidth() != (int)irr_driver->getActualScreenSize().Width)
379 {
380 scaling *= float(viewport.getWidth()) / float(irr_driver->getActualScreenSize().Width); // scale race GUI along screen size
381 }
382 else
383 {
384 scaling *= float(viewport.getWidth()) / 800.0f; // scale race GUI along screen size
385 }
386
387 drawAllMessages(kart, viewport, scaling);
388
389 if(!World::getWorld()->isRacePhase()) return;
390
391 FontDrawer::startBatching();
392 if (!isSpectatorCam)
393 {
394 if (m_multitouch_gui == NULL || m_multitouch_gui->isSpectatorMode())
395 {
396 drawPowerupIcons(kart, viewport, scaling);
397 drawSpeedEnergyRank(kart, viewport, scaling, dt);
398 }
399 }
400
401 if (!m_is_tutorial)
402 drawLap(kart, viewport, scaling);
403 FontDrawer::endBatching();
404 #endif
405 } // renderPlayerView
406
407 //-----------------------------------------------------------------------------
408 /** Displays the racing time on the screen.
409 */
drawGlobalTimer()410 void RaceGUI::drawGlobalTimer()
411 {
412 assert(World::getWorld() != NULL);
413
414 if (!World::getWorld()->shouldDrawTimer())
415 {
416 return;
417 }
418
419 core::stringw sw;
420 video::SColor time_color = video::SColor(255, 255, 255, 255);
421 int dist_from_right = 10 + m_timer_width;
422
423 bool use_digit_font = true;
424
425 float elapsed_time = World::getWorld()->getTime();
426 if (!RaceManager::get()->hasTimeTarget() ||
427 RaceManager::get()->getMinorMode() ==RaceManager::MINOR_MODE_SOCCER ||
428 RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL ||
429 RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_CAPTURE_THE_FLAG)
430 {
431 sw = core::stringw (
432 StringUtils::timeToString(elapsed_time).c_str() );
433 }
434 else
435 {
436 float time_target = RaceManager::get()->getTimeTarget();
437 if (elapsed_time < time_target)
438 {
439 sw = core::stringw (
440 StringUtils::timeToString(time_target - elapsed_time).c_str());
441 }
442 else
443 {
444 sw = _("Challenge Failed");
445 int string_width =
446 GUIEngine::getFont()->getDimension(sw.c_str()).Width;
447 dist_from_right = 10 + string_width;
448 time_color = video::SColor(255,255,0,0);
449 use_digit_font = false;
450 }
451 }
452
453 core::rect<s32> pos(irr_driver->getActualScreenSize().Width - dist_from_right,
454 irr_driver->getActualScreenSize().Height*2/100,
455 irr_driver->getActualScreenSize().Width,
456 irr_driver->getActualScreenSize().Height*6/100);
457
458 // special case : when 3 players play, use available 4th space for such things
459 if (RaceManager::get()->getIfEmptyScreenSpaceExists())
460 {
461 pos -= core::vector2d<s32>(0, pos.LowerRightCorner.Y / 2);
462 pos += core::vector2d<s32>(0, irr_driver->getActualScreenSize().Height - irr_driver->getSplitscreenWindow(0).getHeight());
463 }
464
465 gui::ScalableFont* font = (use_digit_font ? GUIEngine::getHighresDigitFont() : GUIEngine::getFont());
466 font->setScale(1.0f);
467 font->setBlackBorder(true);
468 font->draw(sw, pos, time_color, false, false, NULL,
469 true /* ignore RTL */);
470 font->setBlackBorder(false);
471
472 } // drawGlobalTimer
473
474
475 //-----------------------------------------------------------------------------
476 /** Displays the live difference with a ghost on screen.
477 */
drawLiveDifference()478 void RaceGUI::drawLiveDifference()
479 {
480 assert(World::getWorld() != NULL);
481
482 if (!World::getWorld()->shouldDrawTimer())
483 {
484 return;
485 }
486
487 const LinearWorld *linearworld = dynamic_cast<LinearWorld*>(World::getWorld());
488 assert(linearworld != NULL);
489
490 // Don't display the live difference timer if its time is wrong
491 // (before crossing the start line at start or after crossing it at end)
492 if (!linearworld->hasValidTimeDifference())
493 return;
494
495 float live_difference = linearworld->getLiveTimeDifference();
496
497 int timer_width = m_small_precise_timer_width;
498
499 // 59.9995 is the smallest number of seconds that could get rounded to 1 minute
500 // when rounding at the closest ms
501 if (fabsf(live_difference) >= 59.9995f)
502 timer_width = m_big_precise_timer_width;
503
504 if (live_difference < 0.0f)
505 timer_width += m_negative_timer_additional_width;
506
507 core::stringw sw;
508 video::SColor time_color;
509
510 // Change color depending on value
511 if (live_difference > 1.0f)
512 time_color = video::SColor(255, 255, 0, 0);
513 else if (live_difference > 0.0f)
514 time_color = video::SColor(255, 255, 160, 0);
515 else if (live_difference > -1.0f)
516 time_color = video::SColor(255, 160, 255, 0);
517 else
518 time_color = video::SColor(255, 0, 255, 0);
519
520 int dist_from_right = 10 + timer_width;
521
522 sw = core::stringw (StringUtils::timeToString(live_difference,3,
523 /* display_minutes_if_zero */ false).c_str() );
524
525 core::rect<s32> pos(irr_driver->getActualScreenSize().Width - dist_from_right,
526 irr_driver->getActualScreenSize().Height*7/100,
527 irr_driver->getActualScreenSize().Width,
528 irr_driver->getActualScreenSize().Height*11/100);
529
530 gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
531 font->setScale(1.0f);
532 font->setBlackBorder(true);
533 font->draw(sw.c_str(), pos, time_color, false, false, NULL,
534 true /* ignore RTL */);
535 font->setBlackBorder(false);
536 } // drawLiveDifference
537
538 //-----------------------------------------------------------------------------
539 /** Draws the mini map and the position of all karts on it.
540 */
drawGlobalMiniMap()541 void RaceGUI::drawGlobalMiniMap()
542 {
543 #ifndef SERVER_ONLY
544 //TODO : exception for some game modes ? Another option "Hidden in race, shown in battle ?"
545 if (UserConfigParams::m_minimap_display == 2) /*map hidden*/
546 return;
547
548 if (m_multitouch_gui != NULL && !m_multitouch_gui->isSpectatorMode())
549 {
550 float max_scale = 1.3f;
551
552 if (UserConfigParams::m_multitouch_scale > max_scale)
553 return;
554 }
555
556 // draw a map when arena has a navigation mesh.
557 Track *track = Track::getCurrentTrack();
558 if ( (track->isArena() || track->isSoccer()) && !(track->hasNavMesh()) )
559 return;
560
561 int upper_y = irr_driver->getActualScreenSize().Height - m_map_bottom - m_map_height;
562 int lower_y = irr_driver->getActualScreenSize().Height - m_map_bottom;
563
564 core::rect<s32> dest(m_map_left, upper_y,
565 m_map_left + m_map_width, lower_y);
566
567 track->drawMiniMap(dest);
568
569 World* world = World::getWorld();
570 CaptureTheFlag *ctf_world = dynamic_cast<CaptureTheFlag*>(World::getWorld());
571 SoccerWorld *soccer_world = dynamic_cast<SoccerWorld*>(World::getWorld());
572
573 if (ctf_world)
574 {
575 Vec3 draw_at;
576 if (!ctf_world->isRedFlagInBase())
577 {
578 track->mapPoint2MiniMap(Track::getCurrentTrack()->getRedFlag().getOrigin(),
579 &draw_at);
580 core::rect<s32> rs(core::position2di(0, 0), m_red_flag->getSize());
581 core::rect<s32> rp(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/1.4f)),
582 lower_y -(int)(draw_at.getY()+(m_minimap_player_size/2.2f)),
583 m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/1.4f)),
584 lower_y -(int)(draw_at.getY()-(m_minimap_player_size/2.2f)));
585 draw2DImage(m_red_flag, rp, rs, NULL, NULL, true, true);
586 }
587 Vec3 pos = ctf_world->getRedHolder() == -1 ? ctf_world->getRedFlag() :
588 ctf_world->getKart(ctf_world->getRedHolder())->getSmoothedTrans().getOrigin();
589
590 track->mapPoint2MiniMap(pos, &draw_at);
591 core::rect<s32> rs(core::position2di(0, 0), m_red_flag->getSize());
592 core::rect<s32> rp(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/1.4f)),
593 lower_y -(int)(draw_at.getY()+(m_minimap_player_size/2.2f)),
594 m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/1.4f)),
595 lower_y -(int)(draw_at.getY()-(m_minimap_player_size/2.2f)));
596 draw2DImage(m_red_flag, rp, rs, NULL, NULL, true);
597
598 if (!ctf_world->isBlueFlagInBase())
599 {
600 track->mapPoint2MiniMap(Track::getCurrentTrack()->getBlueFlag().getOrigin(),
601 &draw_at);
602 core::rect<s32> rs(core::position2di(0, 0), m_blue_flag->getSize());
603 core::rect<s32> rp(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/1.4f)),
604 lower_y -(int)(draw_at.getY()+(m_minimap_player_size/2.2f)),
605 m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/1.4f)),
606 lower_y -(int)(draw_at.getY()-(m_minimap_player_size/2.2f)));
607 draw2DImage(m_blue_flag, rp, rs, NULL, NULL, true, true);
608 }
609
610 pos = ctf_world->getBlueHolder() == -1 ? ctf_world->getBlueFlag() :
611 ctf_world->getKart(ctf_world->getBlueHolder())->getSmoothedTrans().getOrigin();
612
613 track->mapPoint2MiniMap(pos, &draw_at);
614 core::rect<s32> bs(core::position2di(0, 0), m_blue_flag->getSize());
615 core::rect<s32> bp(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/1.4f)),
616 lower_y -(int)(draw_at.getY()+(m_minimap_player_size/2.2f)),
617 m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/1.4f)),
618 lower_y -(int)(draw_at.getY()-(m_minimap_player_size/2.2f)));
619 draw2DImage(m_blue_flag, bp, bs, NULL, NULL, true);
620 }
621
622 AbstractKart* target_kart = NULL;
623 Camera* cam = Camera::getActiveCamera();
624 auto cl = LobbyProtocol::get<ClientLobby>();
625 bool is_nw_spectate = cl && cl->isSpectator();
626 // For network spectator highlight
627 if (RaceManager::get()->getNumLocalPlayers() == 1 && cam && is_nw_spectate)
628 target_kart = cam->getKart();
629
630 // Move AI/remote players to the beginning, so that local players icons
631 // are drawn above them
632 World::KartList karts = world->getKarts();
633 std::partition(karts.begin(), karts.end(), [target_kart, is_nw_spectate]
634 (const std::shared_ptr<AbstractKart>& k)->bool
635 {
636 if (is_nw_spectate)
637 return k.get() != target_kart;
638 else
639 return !k->getController()->isLocalPlayerController();
640 });
641
642 for (unsigned int i = 0; i < karts.size(); i++)
643 {
644 const AbstractKart *kart = karts[i].get();
645 const SpareTireAI* sta =
646 dynamic_cast<const SpareTireAI*>(kart->getController());
647
648 // don't draw eliminated kart
649 if (kart->isEliminated() && !(sta && sta->isMoving()))
650 continue;
651 if (!kart->isVisible())
652 continue;
653 const Vec3& xyz = kart->getSmoothedTrans().getOrigin();
654 Vec3 draw_at;
655 track->mapPoint2MiniMap(xyz, &draw_at);
656
657 video::ITexture* icon = sta ? m_heart_icon :
658 kart->getKartProperties()->getMinimapIcon();
659 if (icon == NULL)
660 {
661 continue;
662 }
663 bool is_local = is_nw_spectate ? kart == target_kart :
664 kart->getController()->isLocalPlayerController();
665 // int marker_height = m_marker->getSize().Height;
666 core::rect<s32> source(core::position2di(0, 0), icon->getSize());
667 int marker_half_size = (is_local
668 ? m_minimap_player_size
669 : m_minimap_ai_size )>>1;
670 core::rect<s32> position(m_map_left+(int)(draw_at.getX()-marker_half_size),
671 lower_y -(int)(draw_at.getY()+marker_half_size),
672 m_map_left+(int)(draw_at.getX()+marker_half_size),
673 lower_y -(int)(draw_at.getY()-marker_half_size));
674
675 bool has_teams = (ctf_world || soccer_world);
676
677 // Highlight the player icons with some background image.
678 if ((has_teams || is_local) && m_icons_frame != NULL)
679 {
680 video::SColor color = kart->getKartProperties()->getColor();
681
682 if (has_teams)
683 {
684 KartTeam team = world->getKartTeam(kart->getWorldKartId());
685
686 if (team == KART_TEAM_RED)
687 {
688 color = video::SColor(255, 200, 0, 0);
689 }
690 else if (team == KART_TEAM_BLUE)
691 {
692 color = video::SColor(255, 0, 0, 200);
693 }
694 }
695
696 video::SColor colors[4] = {color, color, color, color};
697
698 const core::rect<s32> rect(core::position2d<s32>(0,0),
699 m_icons_frame->getSize());
700
701 // show kart direction in soccer
702 if (soccer_world)
703 {
704 // Find the direction a kart is moving in
705 btTransform trans = kart->getTrans();
706 Vec3 direction(trans.getBasis().getColumn(2));
707 // Get the rotation to rotate the icon frame
708 float rotation = atan2f(direction.getZ(),direction.getX());
709 if (track->getMinimapInvert())
710 { // correct the direction due to invert minimap for blue
711 rotation = rotation + M_PI;
712 }
713 rotation = -1.0f * rotation + 0.25f * M_PI; // icons-frame_arrow.png was rotated by 45 degrees
714 draw2DImage(m_icons_frame, position, rect, NULL, colors, true, false, rotation);
715 }
716 else
717 {
718 draw2DImage(m_icons_frame, position, rect, NULL, colors, true);
719 }
720 } // if isPlayerController
721
722 draw2DImage(icon, position, source, NULL, NULL, true);
723
724 } // for i<getNumKarts
725
726 // Draw the basket-ball icons on the minimap
727 std::vector<Vec3> basketballs = ProjectileManager::get()->getBasketballPositions();
728 for(unsigned int i = 0; i != basketballs.size(); i++)
729 {
730 Vec3 draw_at;
731 track->mapPoint2MiniMap(basketballs[i], &draw_at);
732
733 video::ITexture* icon = m_basket_ball_icon;
734
735 core::rect<s32> source(core::position2di(0, 0), icon->getSize());
736 int marker_half_size = m_minimap_player_size / 2;
737 core::rect<s32> position(m_map_left+(int)(draw_at.getX()-marker_half_size),
738 lower_y -(int)(draw_at.getY()+marker_half_size),
739 m_map_left+(int)(draw_at.getX()+marker_half_size),
740 lower_y -(int)(draw_at.getY()-marker_half_size));
741
742 draw2DImage(icon, position, source, NULL, NULL, true);
743 }
744
745 // Draw the soccer ball icon
746 if (soccer_world)
747 {
748 Vec3 draw_at;
749 track->mapPoint2MiniMap(soccer_world->getBallPosition(), &draw_at);
750
751 core::rect<s32> source(core::position2di(0, 0), m_soccer_ball->getSize());
752 core::rect<s32> position(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/2.5f)),
753 lower_y -(int)(draw_at.getY()+(m_minimap_player_size/2.5f)),
754 m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/2.5f)),
755 lower_y -(int)(draw_at.getY()-(m_minimap_player_size/2.5f)));
756 draw2DImage(m_soccer_ball, position, source, NULL, NULL, true);
757 }
758 #endif
759 } // drawGlobalMiniMap
760
761 //-----------------------------------------------------------------------------
762 /** Energy meter that gets filled with nitro. This function is called from
763 * drawSpeedEnergyRank, which defines the correct position of the energy
764 * meter.
765 * \param x X position of the meter.
766 * \param y Y position of the meter.
767 * \param kart Kart to display the data for.
768 * \param scaling Scaling applied (in case of split screen)
769 */
drawEnergyMeter(int x,int y,const AbstractKart * kart,const core::recti & viewport,const core::vector2df & scaling)770 void RaceGUI::drawEnergyMeter(int x, int y, const AbstractKart *kart,
771 const core::recti &viewport,
772 const core::vector2df &scaling)
773 {
774 #ifndef SERVER_ONLY
775 float min_ratio = std::min(scaling.X, scaling.Y);
776 const int GAUGEWIDTH = 94;//same inner radius as the inner speedometer circle
777 int gauge_width = (int)(GAUGEWIDTH*min_ratio);
778 int gauge_height = (int)(GAUGEWIDTH*min_ratio);
779
780 float state = (float)(kart->getEnergy())
781 / kart->getKartProperties()->getNitroMax();
782 if (state < 0.0f) state = 0.0f;
783 else if (state > 1.0f) state = 1.0f;
784
785 core::vector2df offset;
786 offset.X = (float)(x-gauge_width) - 9.5f*scaling.X;
787 offset.Y = (float)y-11.5f*scaling.Y;
788
789
790 // Background
791 draw2DImage(m_gauge_empty, core::rect<s32>((int)offset.X,
792 (int)offset.Y-gauge_height,
793 (int)offset.X + gauge_width,
794 (int)offset.Y) /* dest rect */,
795 core::rect<s32>(core::position2d<s32>(0,0),
796 m_gauge_empty->getSize()) /* source rect */,
797 NULL /* clip rect */, NULL /* colors */,
798 true /* alpha */);
799
800 // The positions for A to G are defined here.
801 // They are calculated from gauge_full.png
802 // They are further than the nitrometer farther position because
803 // the lines between them would otherwise cut through the outside circle.
804
805 const int vertices_count = 9;
806
807 core::vector2df position[vertices_count];
808 position[0].X = 0.324f;//A
809 position[0].Y = 0.35f;//A
810 position[1].X = 0.01f;//B1 (margin for gauge goal)
811 position[1].Y = 0.88f;//B1
812 position[2].X = 0.029f;//B2
813 position[2].Y = 0.918f;//B2
814 position[3].X = 0.307f;//C
815 position[3].Y = 0.99f;//C
816 position[4].X = 0.589f;//D
817 position[4].Y = 0.932f;//D
818 position[5].X = 0.818f;//E
819 position[5].Y = 0.755f;//E
820 position[6].X = 0.945f;//F
821 position[6].Y = 0.497f;//F
822 position[7].X = 0.948f;//G1
823 position[7].Y = 0.211f;//G1
824 position[8].X = 0.94f;//G2 (margin for gauge goal)
825 position[8].Y = 0.17f;//G2
826
827 // The states at which different polygons must be used.
828
829 float threshold[vertices_count-2];
830 threshold[0] = 0.0001f; //for gauge drawing
831 threshold[1] = 0.2f;
832 threshold[2] = 0.4f;
833 threshold[3] = 0.6f;
834 threshold[4] = 0.8f;
835 threshold[5] = 0.9999f;
836 threshold[6] = 1.0f;
837
838 // Filling (current state)
839
840 if (state > 0.0f)
841 {
842 video::S3DVertex vertices[vertices_count];
843
844 //3D effect : wait for the full border to appear before drawing
845 for (int i=0;i<5;i++)
846 {
847 if ((state-0.2f*i < 0.006f && state-0.2f*i >= 0.0f) || (0.2f*i-state < 0.003f && 0.2f*i-state >= 0.0f) )
848 {
849 state = 0.2f*i-0.003f;
850 break;
851 }
852 }
853
854 unsigned int count = computeVerticesForMeter(position, threshold, vertices, vertices_count,
855 state, gauge_width, gauge_height, offset);
856
857 if(kart->getControls().getNitro() || kart->isOnMinNitroTime())
858 drawMeterTexture(m_gauge_full_bright, vertices, count);
859 else
860 drawMeterTexture(m_gauge_full, vertices, count);
861 }
862
863 // Target
864
865 if (RaceManager::get()->getCoinTarget() > 0)
866 {
867 float coin_target = (float)RaceManager::get()->getCoinTarget()
868 / kart->getKartProperties()->getNitroMax();
869
870 video::S3DVertex vertices[vertices_count];
871
872 unsigned int count = computeVerticesForMeter(position, threshold, vertices, vertices_count,
873 coin_target, gauge_width, gauge_height, offset);
874
875 drawMeterTexture(m_gauge_goal, vertices, count);
876 }
877 #endif
878 } // drawEnergyMeter
879
880 //-----------------------------------------------------------------------------
881 /** Draws the rank of a player.
882 * \param kart The kart of the player.
883 * \param offset Offset of top left corner for this display (for splitscreen).
884 * \param min_ratio Scaling of the screen (for splitscreen).
885 * \param meter_width Width of the meter (inside which the rank is shown).
886 * \param meter_height Height of the meter (inside which the rank is shown).
887 * \param dt Time step size.
888 */
drawRank(const AbstractKart * kart,const core::vector2df & offset,float min_ratio,int meter_width,int meter_height,float dt)889 void RaceGUI::drawRank(const AbstractKart *kart,
890 const core::vector2df &offset,
891 float min_ratio, int meter_width,
892 int meter_height, float dt)
893 {
894 static video::SColor color = video::SColor(255, 255, 255, 255);
895
896 // Draw rank
897 WorldWithRank *world = dynamic_cast<WorldWithRank*>(World::getWorld());
898 if (!world || !world->displayRank())
899 return;
900
901 int id = kart->getWorldKartId();
902
903 if (m_animation_states[id] == AS_NONE)
904 {
905 if (m_last_ranks[id] != kart->getPosition())
906 {
907 m_rank_animation_duration[id] = 0.0f;
908 m_animation_states[id] = AS_SMALLER;
909 }
910 }
911 else
912 {
913 m_rank_animation_duration[id] += dt;
914 }
915
916 float scale = 1.0f;
917 int rank = kart->getPosition();
918 const float DURATION = 0.4f;
919 const float MIN_SHRINK = 0.3f;
920 if (m_animation_states[id] == AS_SMALLER)
921 {
922 scale = 1.0f - m_rank_animation_duration[id]/ DURATION;
923 rank = m_last_ranks[id];
924 if (scale < MIN_SHRINK)
925 {
926 m_animation_states[id] = AS_BIGGER;
927 m_rank_animation_duration[id] = 0.0f;
928 // Store the new rank
929 m_last_ranks[id] = kart->getPosition();
930 scale = MIN_SHRINK;
931 }
932 }
933 else if (m_animation_states[id] == AS_BIGGER)
934 {
935 scale = m_rank_animation_duration[id] / DURATION + MIN_SHRINK;
936 rank = m_last_ranks[id];
937 if (scale > 1.0f)
938 {
939 m_animation_states[id] = AS_NONE;
940 scale = 1.0f;
941 }
942
943 }
944 else
945 {
946 m_last_ranks[id] = kart->getPosition();
947 }
948
949 gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
950
951 int font_height = font->getDimension(L"X").Height;
952 font->setScale((float)meter_height / font_height * 0.4f * scale);
953 std::ostringstream oss;
954 oss << rank; // the current font has no . :( << ".";
955
956 core::recti pos;
957 pos.LowerRightCorner = core::vector2di(int(offset.X + 0.64f*meter_width),
958 int(offset.Y - 0.49f*meter_height));
959 pos.UpperLeftCorner = core::vector2di(int(offset.X + 0.64f*meter_width),
960 int(offset.Y - 0.49f*meter_height));
961
962 font->setBlackBorder(true);
963 font->draw(oss.str().c_str(), pos, color, true, true);
964 font->setBlackBorder(false);
965 font->setScale(1.0f);
966 } // drawRank
967
968 //-----------------------------------------------------------------------------
969 /** Draws the speedometer, the display of available nitro, and
970 * the rank of the kart (inside the speedometer).
971 * \param kart The kart for which to show the data.
972 * \param viewport The viewport to use.
973 * \param scaling Which scaling to apply to the speedometer.
974 * \param dt Time step size.
975 */
drawSpeedEnergyRank(const AbstractKart * kart,const core::recti & viewport,const core::vector2df & scaling,float dt)976 void RaceGUI::drawSpeedEnergyRank(const AbstractKart* kart,
977 const core::recti &viewport,
978 const core::vector2df &scaling,
979 float dt)
980 {
981 #ifndef SERVER_ONLY
982 float min_ratio = std::min(scaling.X, scaling.Y);
983 const int SPEEDWIDTH = 128;
984 int meter_width = (int)(SPEEDWIDTH*min_ratio);
985 int meter_height = (int)(SPEEDWIDTH*min_ratio);
986
987 drawEnergyMeter(viewport.LowerRightCorner.X ,
988 (int)(viewport.LowerRightCorner.Y),
989 kart, viewport, scaling);
990
991 // First draw the meter (i.e. the background )
992 // -------------------------------------------------------------------------
993 core::vector2df offset;
994 offset.X = (float)(viewport.LowerRightCorner.X-meter_width) - 24.0f*scaling.X;
995 offset.Y = viewport.LowerRightCorner.Y-10.0f*scaling.Y;
996
997 const core::rect<s32> meter_pos((int)offset.X,
998 (int)(offset.Y-meter_height),
999 (int)(offset.X+meter_width),
1000 (int)offset.Y);
1001 video::ITexture *meter_texture = m_speed_meter_icon->getTexture();
1002 const core::rect<s32> meter_texture_coords(core::position2d<s32>(0,0),
1003 meter_texture->getSize());
1004 draw2DImage(meter_texture, meter_pos, meter_texture_coords, NULL,
1005 NULL, true);
1006 // TODO: temporary workaround, shouldn't have to use
1007 // draw2DVertexPrimitiveList to render a simple rectangle
1008
1009 const float speed = kart->getSpeed();
1010
1011 drawRank(kart, offset, min_ratio, meter_width, meter_height, dt);
1012
1013
1014 if(speed <=0) return; // Nothing to do if speed is negative.
1015
1016 // Draw the actual speed bar (if the speed is >0)
1017 // ----------------------------------------------
1018 float speed_ratio = speed/40.0f; //max displayed speed of 40
1019 if(speed_ratio>1) speed_ratio = 1;
1020
1021 // see computeVerticesForMeter for the detail of the drawing
1022 // If increasing this, update drawMeterTexture
1023
1024 const int vertices_count = 12;
1025
1026 video::S3DVertex vertices[vertices_count];
1027
1028 // The positions for A to J2 are defined here.
1029
1030 // They are calculated from speedometer.png
1031 // A is the center of the speedometer's circle
1032 // B2, C, D, E, F, G, H, I and J1 are points on the line
1033 // from A to their respective 1/8th threshold division
1034 // B2 is 36,9° clockwise from the vertical (on bottom-left)
1035 // J1 s 70,7° clockwise from the vertical (on upper-right)
1036 // B1 and J2 are used for correct display of the 3D effect
1037 // They are 1,13* further than the speedometer farther position because
1038 // the lines between them would otherwise cut through the outside circle.
1039
1040 core::vector2df position[vertices_count];
1041
1042 position[0].X = 0.546f;//A
1043 position[0].Y = 0.566f;//A
1044 position[1].X = 0.216f;//B1
1045 position[1].Y = 1.036f;//B1
1046 position[2].X = 0.201f;//B2
1047 position[2].Y = 1.023f;//B2
1048 position[3].X = 0.036f;//C
1049 position[3].Y = 0.831f;//C
1050 position[4].X = -0.029f;//D
1051 position[4].Y = 0.589f;//D
1052 position[5].X = 0.018f;//E
1053 position[5].Y = 0.337f;//E
1054 position[6].X = 0.169f;//F
1055 position[6].Y = 0.134f;//F
1056 position[7].X = 0.391f;//G
1057 position[7].Y = 0.014f;//G
1058 position[8].X = 0.642f;//H
1059 position[8].Y = 0.0f;//H
1060 position[9].X = 0.878f;//I
1061 position[9].Y = 0.098f;//I
1062 position[10].X = 1.046f;//J1
1063 position[10].Y = 0.285f;//J1
1064 position[11].X = 1.052f;//J2
1065 position[11].Y = 0.297f;//J2
1066
1067 // The speed ratios at which different triangles must be used.
1068
1069 float threshold[vertices_count-2];
1070 threshold[0] = 0.00001f;//for the 3D margin
1071 threshold[1] = 0.125f;
1072 threshold[2] = 0.25f;
1073 threshold[3] = 0.375f;
1074 threshold[4] = 0.50f;
1075 threshold[5] = 0.625f;
1076 threshold[6] = 0.750f;
1077 threshold[7] = 0.875f;
1078 threshold[8] = 0.99999f;//for the 3D margin
1079 threshold[9] = 1.0f;
1080
1081 //3D effect : wait for the full border to appear before drawing
1082 for (int i=0;i<8;i++)
1083 {
1084 if ((speed_ratio-0.125f*i < 0.00625f && speed_ratio-0.125f*i >= 0.0f) || (0.125f*i-speed_ratio < 0.0045f && 0.125f*i-speed_ratio >= 0.0f) )
1085 {
1086 speed_ratio = 0.125f*i-0.0045f;
1087 break;
1088 }
1089 }
1090
1091 unsigned int count = computeVerticesForMeter(position, threshold, vertices, vertices_count,
1092 speed_ratio, meter_width, meter_height, offset);
1093
1094
1095 drawMeterTexture(m_speed_bar_icon->getTexture(), vertices, count);
1096 #endif
1097 } // drawSpeedEnergyRank
1098
drawMeterTexture(video::ITexture * meter_texture,video::S3DVertex vertices[],unsigned int count)1099 void RaceGUI::drawMeterTexture(video::ITexture *meter_texture, video::S3DVertex vertices[], unsigned int count)
1100 {
1101 #ifndef SERVER_ONLY
1102 //Should be greater or equal than the greatest vertices_count used by the meter functions
1103 if (count < 2)
1104 return;
1105 short int index[12];
1106 for(unsigned int i=0; i<count; i++)
1107 {
1108 index[i]=i;
1109 vertices[i].Color = video::SColor(255, 255, 255, 255);
1110 }
1111
1112 video::SMaterial m;
1113 m.setTexture(0, meter_texture);
1114 m.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
1115 irr_driver->getVideoDriver()->setMaterial(m);
1116 glEnable(GL_BLEND);
1117 glDisable(GL_CULL_FACE);
1118
1119 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
1120 draw2DVertexPrimitiveList(m.getTexture(0), vertices, count,
1121 index, count-2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN);
1122 glDisable(GL_BLEND);
1123 glEnable(GL_CULL_FACE);
1124
1125 #endif
1126 } // drawMeterTexture
1127
1128
1129
1130 //-----------------------------------------------------------------------------
1131 /** This function computes a polygon used for drawing the measure for a meter (speedometer, etc.)
1132 * The variable measured by the meter is compared to the thresholds, and is then used to
1133 * compute a point between the two points associated with the lower and upper threshold
1134 * Then, a polygon is calculated linking all the previous points and the variable point
1135 * which link back to the first point. This polygon is used for drawing.
1136 *
1137 * Consider the following example :
1138 *
1139 * A E
1140 * -|
1141 * x
1142 * |
1143 * -D-|
1144 * -w-|
1145 * |-C--|
1146 * -B--v-|
1147 *
1148 * If the measure is inferior to the first threshold, the function will create a triangle ABv
1149 * with the position of v varying proportionally on a line between B and C ;
1150 * at B with 0 and at C when it reaches the first threshold.
1151 * If the measure is between the first and second thresholds, the function will create a quad ABCw,
1152 * with w varying in the same way than v.
1153 * If the measure exceds the higher threshold, the function will return the poly ABCDE.
1154 *
1155 * \param position The relative positions of the vertices.
1156 * \param threshold The thresholds at which the variable point switch from a segment to the next.
1157 * The size of this array should be smaller by two than the position array.
1158 * The last threshold determines the measure over which the meter is full
1159 * \param vertices Where the results of the computation are put, for use by the calling function.
1160 * \param vertices_count The maximum number of vertices to use. Should be superior or equal to the
1161 * size of the arrays.
1162 * \param measure The value of the variable measured by the meter.
1163 * \param gauge_width The width of the meter
1164 * \param gauge_height The height of the meter
1165 * \param offset The offset to position the meter
1166 */
computeVerticesForMeter(core::vector2df position[],float threshold[],video::S3DVertex vertices[],unsigned int vertices_count,float measure,int gauge_width,int gauge_height,core::vector2df offset)1167 unsigned int RaceGUI::computeVerticesForMeter(core::vector2df position[], float threshold[], video::S3DVertex vertices[], unsigned int vertices_count,
1168 float measure, int gauge_width, int gauge_height, core::vector2df offset)
1169 {
1170 //Nothing to draw ; we need at least three points to draw a triangle
1171 if (vertices_count <= 2 || measure < 0)
1172 {
1173 return 0;
1174 }
1175
1176 unsigned int count=2;
1177 float f = 1.0f;
1178
1179 for (unsigned int i=2 ; i < vertices_count ; i++)
1180 {
1181 count++;
1182
1183 //Stop when we have found between which thresholds the measure is
1184 if (measure < threshold[i-2])
1185 {
1186 if (i-2 == 0)
1187 {
1188 f = measure/threshold[i-2];
1189 }
1190 else
1191 {
1192 f = (measure - threshold[i-3])/(threshold[i-2]-threshold[i-3]);
1193 }
1194
1195 break;
1196 }
1197 }
1198
1199 for (unsigned int i=0 ; i < count ; i++)
1200 {
1201 //if the measure don't fall in this segment, use the next predefined point
1202 if (i<count-1 || (count == vertices_count && f == 1.0f))
1203 {
1204 vertices[i].TCoords = core::vector2df(position[i].X, position[i].Y);
1205 vertices[i].Pos = core::vector3df(offset.X+position[i].X*gauge_width,
1206 offset.Y-(1-position[i].Y)*gauge_height, 0);
1207 }
1208 //if the measure fall in this segment, compute the variable position
1209 else
1210 {
1211 //f : the proportion of the next point. 1-f : the proportion of the previous point
1212 vertices[i].TCoords = core::vector2df(position[i].X*(f)+position[i-1].X*(1.0f-f),
1213 position[i].Y*(f)+position[i-1].Y*(1.0f-f));
1214 vertices[i].Pos = core::vector3df(offset.X+ ((position[i].X*(f)+position[i-1].X*(1.0f-f))*gauge_width),
1215 offset.Y-(((1-position[i].Y)*(f)+(1-position[i-1].Y)*(1.0f-f))*gauge_height),0);
1216 }
1217 }
1218
1219 //the count is used in the drawing functions
1220 return count;
1221 } //computeVerticesForMeter
1222
1223 //-----------------------------------------------------------------------------
1224 /** Displays the lap of the kart.
1225 * \param info Info object c
1226 */
drawLap(const AbstractKart * kart,const core::recti & viewport,const core::vector2df & scaling)1227 void RaceGUI::drawLap(const AbstractKart* kart,
1228 const core::recti &viewport,
1229 const core::vector2df &scaling)
1230 {
1231 #ifndef SERVER_ONLY
1232 // Don't display laps if the kart has already finished the race.
1233 if (kart->hasFinishedRace()) return;
1234
1235 World *world = World::getWorld();
1236
1237 core::recti pos;
1238
1239 pos.UpperLeftCorner.Y = viewport.UpperLeftCorner.Y + m_font_height;
1240
1241 // If the time display in the top right is in this viewport,
1242 // move the lap/rank display down a little bit so that it is
1243 // displayed under the time.
1244 if (viewport.UpperLeftCorner.Y == 0 &&
1245 viewport.LowerRightCorner.X == (int)(irr_driver->getActualScreenSize().Width) &&
1246 !RaceManager::get()->getIfEmptyScreenSpaceExists())
1247 {
1248 pos.UpperLeftCorner.Y = irr_driver->getActualScreenSize().Height*12/100;
1249 }
1250 pos.LowerRightCorner.Y = viewport.LowerRightCorner.Y+20;
1251 pos.UpperLeftCorner.X = viewport.LowerRightCorner.X
1252 - m_lap_width - 10;
1253 pos.LowerRightCorner.X = viewport.LowerRightCorner.X;
1254
1255 // Draw CTF / soccer scores with red score - blue score (score limit)
1256 CaptureTheFlag* ctf = dynamic_cast<CaptureTheFlag*>(World::getWorld());
1257 SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
1258 FreeForAll* ffa = dynamic_cast<FreeForAll*>(World::getWorld());
1259
1260 static video::SColor color = video::SColor(255, 255, 255, 255);
1261 int hit_capture_limit =
1262 (RaceManager::get()->getHitCaptureLimit() != std::numeric_limits<int>::max()
1263 && RaceManager::get()->getHitCaptureLimit() != 0)
1264 ? RaceManager::get()->getHitCaptureLimit() : -1;
1265 int score_limit = sw && !RaceManager::get()->hasTimeTarget() ?
1266 RaceManager::get()->getMaxGoal() : ctf ? hit_capture_limit : -1;
1267 if (!ctf && ffa && hit_capture_limit != -1)
1268 {
1269 int icon_width = irr_driver->getActualScreenSize().Height/19;
1270 core::rect<s32> indicator_pos(viewport.LowerRightCorner.X - (icon_width+10),
1271 pos.UpperLeftCorner.Y,
1272 viewport.LowerRightCorner.X - 10,
1273 pos.UpperLeftCorner.Y + icon_width);
1274 core::rect<s32> source_rect(core::position2d<s32>(0,0),
1275 m_champion->getSize());
1276 draw2DImage(m_champion, indicator_pos, source_rect,
1277 NULL, NULL, true);
1278
1279 gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
1280 font->setBlackBorder(true);
1281 pos.UpperLeftCorner.X += 30;
1282 font->draw(StringUtils::toWString(hit_capture_limit).c_str(), pos, color);
1283 font->setBlackBorder(false);
1284 font->setScale(1.0f);
1285 return;
1286 }
1287
1288 if (ctf || sw)
1289 {
1290 int red_score = ctf ? ctf->getRedScore() : sw->getScore(KART_TEAM_RED);
1291 int blue_score = ctf ? ctf->getBlueScore() : sw->getScore(KART_TEAM_BLUE);
1292 gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
1293 font->setBlackBorder(true);
1294 font->setScale(1.0f);
1295 core::dimension2du d;
1296 if (score_limit != -1)
1297 {
1298 d = font->getDimension(
1299 (StringUtils::toWString(red_score) + L"-"
1300 + StringUtils::toWString(blue_score) + L" "
1301 + StringUtils::toWString(score_limit)).c_str());
1302 pos.UpperLeftCorner.X -= d.Width / 2;
1303 int icon_width = irr_driver->getActualScreenSize().Height/19;
1304 core::rect<s32> indicator_pos(viewport.LowerRightCorner.X - (icon_width+10),
1305 pos.UpperLeftCorner.Y,
1306 viewport.LowerRightCorner.X - 10,
1307 pos.UpperLeftCorner.Y + icon_width);
1308 core::rect<s32> source_rect(core::position2d<s32>(0,0),
1309 m_champion->getSize());
1310 draw2DImage(m_champion, indicator_pos, source_rect,
1311 NULL, NULL, true);
1312 }
1313
1314 core::stringw text = StringUtils::toWString(red_score);
1315 font->draw(text, pos, video::SColor(255, 255, 0, 0));
1316 d = font->getDimension(text.c_str());
1317 pos += core::position2di(d.Width, 0);
1318 text = L"-";
1319 font->draw(text, pos, video::SColor(255, 255, 255, 255));
1320 d = font->getDimension(text.c_str());
1321 pos += core::position2di(d.Width, 0);
1322 text = StringUtils::toWString(blue_score);
1323 font->draw(text, pos, video::SColor(255, 0, 0, 255));
1324 pos += core::position2di(d.Width, 0);
1325 if (score_limit != -1)
1326 {
1327 text = L" ";
1328 text += StringUtils::toWString(score_limit);
1329 font->draw(text, pos, video::SColor(255, 255, 255, 255));
1330 }
1331 font->setBlackBorder(false);
1332 return;
1333 }
1334
1335 if (!world->raceHasLaps()) return;
1336 int lap = world->getFinishedLapsOfKart(kart->getWorldKartId());
1337 // Network race has larger lap than getNumLaps near finish line
1338 // due to waiting for final race result from server
1339 if (lap + 1> RaceManager::get()->getNumLaps())
1340 lap--;
1341 // don't display 'lap 0/..' at the start of a race
1342 if (lap < 0 ) return;
1343
1344 // Display lap flag
1345
1346
1347 int icon_width = irr_driver->getActualScreenSize().Height/19;
1348 core::rect<s32> indicator_pos(viewport.LowerRightCorner.X - (icon_width+10),
1349 pos.UpperLeftCorner.Y,
1350 viewport.LowerRightCorner.X - 10,
1351 pos.UpperLeftCorner.Y + icon_width);
1352 core::rect<s32> source_rect(core::position2d<s32>(0,0),
1353 m_lap_flag->getSize());
1354 draw2DImage(m_lap_flag,indicator_pos,source_rect,
1355 NULL,NULL,true);
1356
1357 pos.UpperLeftCorner.X -= icon_width;
1358 pos.LowerRightCorner.X -= icon_width;
1359
1360 std::ostringstream out;
1361 out << lap + 1 << "/" << RaceManager::get()->getNumLaps();
1362
1363 gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
1364 font->setBlackBorder(true);
1365 font->draw(out.str().c_str(), pos, color);
1366 font->setBlackBorder(false);
1367 font->setScale(1.0f);
1368 #endif
1369 } // drawLap
1370
1371