// // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2004-2015 Steve Baker // Copyright (C) 2006-2015 Joerg Henrichs, SuperTuxKart-Team, Steve Baker // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 3 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "states_screens/race_gui_base.hpp" #include "audio/music_manager.hpp" #include "config/user_config.hpp" #include "graphics/2dutils.hpp" #include "graphics/camera.hpp" #include "graphics/central_settings.hpp" #include "graphics/irr_driver.hpp" #include "graphics/material.hpp" #include "graphics/material_manager.hpp" #include "graphics/referee.hpp" #include "guiengine/scalable_font.hpp" #include "io/file_manager.hpp" #include "items/attachment_manager.hpp" #include "items/powerup.hpp" #include "karts/abstract_kart.hpp" #include "karts/abstract_kart_animation.hpp" #include "karts/controller/controller.hpp" #include "karts/explosion_animation.hpp" #include "karts/kart_properties.hpp" #include "karts/kart_properties_manager.hpp" #include "karts/rescue_animation.hpp" #include "modes/capture_the_flag.hpp" #include "modes/linear_world.hpp" #include "modes/world.hpp" #include "network/network_config.hpp" #include "states_screens/race_gui_multitouch.hpp" #include "tracks/track.hpp" #include "utils/constants.hpp" #include namespace irr { namespace video { extern bool useCoreContext; } } RaceGUIBase::RaceGUIBase() { m_ignore_unimportant_messages = false; m_max_font_height = GUIEngine::getFontHeight() + 10; m_small_font_max_height = GUIEngine::getSmallFontHeight() + 5; //I18N: as in "ready, set, go", shown at the beginning of the race m_string_ready = _("Ready!"); //I18N: as in "ready, set, go", shown at the beginning of the race m_string_set = _("Set!"); //I18N: as in "ready, set, go", shown at the beginning of the race m_string_go = _("Go!"); //I18N: Shown when a goal is scored m_string_goal = _("GOAL!"); // I18N: Shown waiting for other players in network to finish loading or // waiting m_string_waiting_for_others = _("Waiting for others"); m_music_icon = irr_driver->getTexture("notes.png"); if (!m_music_icon) { Log::error("RaceGuiBase", "Can't find 'notes.png' texture, aborting."); } m_plunger_face = irr_driver->getTexture("plungerface.png"); if (!m_plunger_face) { Log::error("RaceGuiBase", "Can't find 'plungerface.png' texture, aborting."); } //read frame picture for icons in the mini map. m_icons_frame = irr_driver->getTexture("icons-frame.png"); if (!m_icons_frame) { Log::error("RaceGuiBase", "Can't find 'icons-frame.png' texture, aborting."); } m_gauge_full = irr_driver->getTexture(FileManager::GUI_ICON, "gauge_full.png"); m_gauge_full_bright = irr_driver->getTexture(FileManager::GUI_ICON, "gauge_full_bright.png"); m_gauge_empty = irr_driver->getTexture(FileManager::GUI_ICON, "gauge_empty.png"); m_gauge_goal = irr_driver->getTexture(FileManager::GUI_ICON, "gauge_goal.png"); m_lap_flag = irr_driver->getTexture(FileManager::GUI_ICON, "lap_flag.png"); m_dist_show_overlap = 2; m_icons_inertia = 2; m_referee = NULL; m_multitouch_gui = NULL; } // RaceGUIBase // ---------------------------------------------------------------------------- /** This is a second initialisation call (after the constructor) for the race * gui. This is called after the world has been initialised, e.g. all karts * do exist (while the constructor is called before the karts are created). * In the base gui this is used to initialise the referee data (which needs * the start positions of the karts). Note that this function is (and must * be called only once, after its constructor). * \pre All karts must be created, since this object defines the * positions for the referees based on the karts. */ void RaceGUIBase::init() { m_kart_display_infos.resize(race_manager->getNumberOfKarts()); // Do everything else required at a race restart as well, esp. // resetting the height of the referee. m_referee = new Referee(); m_referee_pos.resize(race_manager->getNumberOfKarts()); m_referee_rotation.resize(race_manager->getNumberOfKarts()); } // init //----------------------------------------------------------------------------- /** This is called when restarting a race. In the race gui base it resets * height of the referee, so that it can start flying down again. */ void RaceGUIBase::reset() { // While we actually only need the positions for local players, // we add all karts, since it's easier to get a world kart id from // the kart then the local player id (and it avoids problems in // profile mode where there might be a camera, but no player). for(unsigned int i=0; igetNumberOfKarts(); i++) { const AbstractKart *kart = World::getWorld()->getKart(i); m_referee_pos[i] = kart->getTrans()(Referee::getStartOffset()); Vec3 hpr; btQuaternion q = btQuaternion(kart->getTrans().getBasis().getColumn(1), Referee::getStartRotation().getY() * DEGREE_TO_RAD) * kart->getTrans().getRotation(); hpr.setHPR(q); m_referee_rotation[i] = hpr.toIrrHPR(); } m_referee_height = 10.0f; m_referee->attachToSceneNode(); m_referee->selectReadySetGo(0); // set red color m_plunger_move_time = 0; m_plunger_offset = core::vector2di(0,0); m_plunger_speed = core::vector2df(0,0); m_plunger_state = PLUNGER_STATE_INIT; clearAllMessages(); if (m_multitouch_gui != NULL) { m_multitouch_gui->reset(); } } // reset //----------------------------------------------------------------------------- /** The destructor removes the marker texture and the referee scene node. */ RaceGUIBase::~RaceGUIBase() { //irr_driver->removeTexture(m_marker); // If the referee is currently being shown, // remove it from the scene graph. delete m_referee; } // ~RaceGUIBase //----------------------------------------------------------------------------- /** Creates the 2D vertices for a regular polygon. Adopted from Irrlicht. * \param n Number of vertices to use. * \param radius Radius of the polygon. * \param center The center point of the polygon. * \param v Pointer to the array of vertices. */ void RaceGUIBase::createRegularPolygon(unsigned int n, float radius, const core::vector2df ¢er, const video::SColor &color, video::S3DVertex *v, unsigned short int *index) { float f = 2*M_PI/(float)n; for (unsigned int i=0; igetNumLocalPlayers() < 2 && !m_ignore_unimportant_messages) { for (AllMessageType::const_iterator i = m_messages.begin(); i != m_messages.end(); ++i) { TimedMessage const &msg = *i; if (!msg.m_important) { // Display only messages for all karts, or messages for this kart if (msg.m_kart && msg.m_kart!=kart) continue; core::rect pos(x - w/2, y, x + w/2, y + m_max_font_height); gui::ScalableFont* font = GUIEngine::getSmallFont(); if (msg.m_outline) font->setBlackBorder(true); font->draw( core::stringw(msg.m_message.c_str()).c_str(), pos, msg.m_color, true /* hcenter */, true /* vcenter */); if (msg.m_outline) font->setBlackBorder(false); y -= m_small_font_max_height; } } } // First line of text somewhat under the top of the viewport. y = viewport.UpperLeftCorner.Y + (viewport.LowerRightCorner.Y - viewport.UpperLeftCorner.Y)/4; gui::ScalableFont* font = GUIEngine::getFont(); gui::ScalableFont* big_font = GUIEngine::getTitleFont(); int font_height = m_max_font_height; if (race_manager->getNumLocalPlayers() > 2) { font = GUIEngine::getSmallFont(); } irr_driver->getVideoDriver()->enableMaterial2D(); // seems like we need to remind irrlicht from time to time to use the Material2D // The message are displayed in reverse order, so that a multi-line // message (addMessage("1", ...); addMessage("2",...) is displayed // in the right order: "1" on top of "2" for (AllMessageType::const_iterator i = m_messages.begin(); i != m_messages.end(); ++i) { TimedMessage const &msg = *i; // less important messages were already displayed if (!msg.m_important) continue; // Display only messages for all karts, or messages for this kart if (msg.m_kart && msg.m_kart!=kart) continue; core::rect pos(x - w/2, y, x + w/2, y + font_height); if (msg.m_big_font) { big_font->draw(core::stringw(msg.m_message.c_str()).c_str(), pos, msg.m_color, true /* hcenter */, true /* vcenter */); y += GUIEngine::getTitleFontHeight(); } else { if (msg.m_outline) font->setBlackBorder(true); font->draw(core::stringw(msg.m_message.c_str()).c_str(), pos, msg.m_color, true /* hcenter */, true /* vcenter */); if (msg.m_outline) font->setBlackBorder(false); y += font_height; } } // for i in all messages } // drawAllMessages //----------------------------------------------------------------------------- /** Removes messages which have been displayed long enough. This function * must be called after drawAllMessages, otherwise messages which are only * displayed once will not be drawn! */ void RaceGUIBase::cleanupMessages(const float dt) { AllMessageType::iterator p =m_messages.begin(); while(p!=m_messages.end()) { if((*p).done(dt)) { p = m_messages.erase(p); } else { ++p; } } } // cleanupMessages //----------------------------------------------------------------------------- /** Draws the powerup icons on the screen (called once for each player). * \param kart The kart for which to draw the powerup icons. * \param viewport The viewport into which to draw the icons. * \param scaling The scaling to use when drawing the icons. */ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart, const core::recti &viewport, const core::vector2df &scaling) { #ifndef SERVER_ONLY // If player doesn't have any powerups or has completed race, do nothing. const Powerup* powerup = kart->getPowerup(); if (powerup->getType() == PowerupManager::POWERUP_NOTHING || kart->hasFinishedRace()) return; int n = kart->getPowerup()->getNum() ; int many_powerups = 0; if (n<1) return; // shouldn't happen, but just in case if (n>5) { many_powerups = n; n = 1; } float scale = (float)(std::min(scaling.X, scaling.Y)); int nSize = (int)(64.0f * scale); int itemSpacing = (int)(scale * 32.0f); int x1, y1; // When there is not much height, move items on the side if ((float) viewport.getWidth() / (float) viewport.getHeight() > 2.0f) { x1 = viewport.UpperLeftCorner.X + 3*(viewport.getWidth()/4) - ((n * itemSpacing)/2); } else { x1 = viewport.UpperLeftCorner.X + (viewport.getWidth()/2) - ((n * itemSpacing)/2); } // When the viewport is smaller in splitscreen, reduce the top margin if ((race_manager->getNumLocalPlayers() == 2 && viewport.getWidth() > viewport.getHeight()) || race_manager->getNumLocalPlayers() >= 3 ) y1 = viewport.UpperLeftCorner.Y + (int)(5 * scaling.Y); else y1 = viewport.UpperLeftCorner.Y + (int)(20 * scaling.Y); int x2 = 0; assert(powerup != NULL); assert(powerup->getIcon() != NULL); video::ITexture *t=powerup->getIcon()->getTexture(); assert(t != NULL); core::rect rect(core::position2di(0, 0), t->getSize()); for ( int i = 0 ; i < n ; i++ ) { x2 = (int)((x1+i*itemSpacing) - (itemSpacing / 2)); core::rect pos(x2, y1, x2+nSize, y1+nSize); draw2DImage(t, pos, rect, NULL, NULL, true); } // for i if (many_powerups > 0) { gui::ScalableFont* font = GUIEngine::getHighresDigitFont(); core::rect pos(x2+nSize, y1, x2+nSize+nSize, y1+nSize); font->setScale(scale); font->draw(core::stringw(L"x")+StringUtils::toWString(many_powerups), pos, video::SColor(255, 255, 255, 255)); font->setScale(1.0f); } #endif } // drawPowerupIcons // ---------------------------------------------------------------------------- /** Updates lightning related information. */ void RaceGUIBase::renderGlobal(float dt) { } // renderGlobal // ---------------------------------------------------------------------------- /** Update, called once per frame. This updates the height of the referee * (to create a flying down animation). * \param dt Time step size. */ void RaceGUIBase::update(float dt) { // E.g. a race result gui does not have a referee if(m_referee) { World *world = World::getWorld(); // During GO move the referee up again if(world->getPhase()==World::SETUP_PHASE) { m_referee_height = 10.0f; m_referee->selectReadySetGo(0); // set red color } else if(world->getPhase()==World::GO_PHASE) { m_referee_height += dt*5.0f; m_referee->selectReadySetGo(2); } else if (world->getPhase()==World::WAIT_FOR_SERVER_PHASE || (NetworkConfig::get()->isNetworking() && world->getPhase()==World::TRACK_INTRO_PHASE)) { } else if ((!NetworkConfig::get()->isNetworking() && world->getPhase()==World::TRACK_INTRO_PHASE) || (NetworkConfig::get()->isNetworking() && world->getPhase()==World::SERVER_READY_PHASE)) { m_referee->selectReadySetGo(0); // set red color m_referee_height -= dt*5.0f; if(m_referee_height<0) m_referee_height = 0; } else if(world->isStartPhase()) // must be ready or set now { m_referee_height = 0; m_referee->selectReadySetGo(world->getPhase()==World::SET_PHASE ? 1 : 0); } else if(world->getPhase()==World::IN_GAME_MENU_PHASE) { // Don't do anything, without this the next clause // would completely remove thunderbird. } else if(m_referee->isAttached()) // race phase: { m_referee->removeFromSceneGraph(); } } // if referee node } // update // ---------------------------------------------------------------------------- /** This function is called just before rendering the view for each kart. This * is used here to display the referee during the ready-set-go phase. * \param kart The kart whose view is rendered next. */ void RaceGUIBase::preRenderCallback(const Camera *camera) { if(m_referee && camera->getKart()) { unsigned int world_id = camera->getKart()->getWorldKartId(); Vec3 xyz = m_referee_pos[world_id] + camera->getKart()->getNormal() * m_referee_height; m_referee->setPosition(xyz); m_referee->setRotation(m_referee_rotation[world_id]); } } // preRenderCallback // ---------------------------------------------------------------------------- void RaceGUIBase::renderPlayerView(const Camera *camera, float dt) { const core::recti &viewport = camera->getViewport(); const core::vector2df scaling = camera->getScaling(); const AbstractKart* kart = camera->getKart(); if(!kart) return; if (m_multitouch_gui != NULL) { m_multitouch_gui->draw(kart, viewport, scaling); } } // renderPlayerView //----------------------------------------------------------------------------- /** Adds a message to the message queue. The message is displayed for a * certain amount of time (unless time<0, then the message is displayed * once). */ void RaceGUIBase::addMessage(const core::stringw &msg, const AbstractKart *kart, float time, const video::SColor &color, bool important, bool big_font, bool outline) { m_messages.push_back(TimedMessage(msg, kart, time, color, important, big_font, outline)); } // addMessage //----------------------------------------------------------------------------- /** Displays the description given for the music currently being played. * This is usually the title and composer. */ void RaceGUIBase::drawGlobalMusicDescription() { #ifndef SERVER_ONLY // show no music description when it's off if (!UserConfigParams::m_music) return; gui::IGUIFont* font = GUIEngine::getFont(); float race_time = stk_config->ticks2Time(World::getWorld()->getTicksSinceStart()); // ---- Manage pulsing effect // 3.0 is the duration of ready/set (TODO: don't hardcode) float timeProgression = (float)(race_time) / (float)(stk_config->m_music_credit_time - 2.0f); const int x_pulse = (int)(sin(race_time*9.0f)*10.0f); const int y_pulse = (int)(cos(race_time*9.0f)*10.0f); float resize = 1.0f; if (timeProgression < 0.1) { resize = timeProgression/0.1f; } else if (timeProgression > 0.9) { resize = 1.0f - (timeProgression - 0.9f)/0.1f; } const float resize3 = resize*resize*resize; // Get song name, and calculate its size, allowing us to position stuff const MusicInformation* mi = music_manager->getCurrentMusic(); if (!mi) return; core::stringw thetext = core::stringw(L"\"") + mi->getTitle() + L"\""; core::dimension2d< u32 > textSize = font->getDimension(thetext.c_str()); int textWidth = textSize.Width; int textWidth2 = 0; core::stringw thetext_composer; if (mi->getComposer()!="") { // I18N: string used to show the author of the music. (e.g. "Sunny Song" by "John Doe") thetext_composer = _("by"); thetext_composer += " "; thetext_composer += mi->getComposer().c_str(); textWidth2 = font->getDimension(thetext_composer.c_str()).Width; } const int max_text_size = (int)(irr_driver->getActualScreenSize().Width*2.0f/3.0f); if (textWidth > max_text_size) textWidth = max_text_size; if (textWidth2 > max_text_size) textWidth2 = max_text_size; const int ICON_SIZE = 64; const int y = irr_driver->getActualScreenSize().Height - 80; // the 20 is an arbitrary space left between the note icon and the text const int noteX = (irr_driver->getActualScreenSize().Width / 2) - std::max(textWidth, textWidth2)/2 - ICON_SIZE/2 - 20; const int noteY = y; // the 20 is an arbitrary space left between the note icon and the text const int textXFrom = (irr_driver->getActualScreenSize().Width / 2) - std::max(textWidth, textWidth2)/2 + 20; const int textXTo = (irr_driver->getActualScreenSize().Width / 2) + std::max(textWidth, textWidth2)/2 + 20; // ---- Draw "by" text const int text_y = (int)(irr_driver->getActualScreenSize().Height - 80*(resize3) + 40*(1-resize)); static const video::SColor white = video::SColor(255, 255, 255, 255); if(mi->getComposer()!="") { core::rect pos_by(textXFrom, text_y+40, textXTo, text_y+40); font->draw(thetext_composer, pos_by, white, true, true); } // ---- Draw "song name" text core::rect pos(textXFrom, text_y, textXTo, text_y); font->draw(thetext.c_str(), pos, white, true /* hcenter */, true /* vcenter */); // Draw music icon if (m_music_icon != NULL) { int iconSizeX = (int)(ICON_SIZE*resize + x_pulse*resize*resize); int iconSizeY = (int)(ICON_SIZE*resize + y_pulse*resize*resize); core::rect dest(noteX-iconSizeX/2+20, noteY-iconSizeY/2+ICON_SIZE/2, noteX+iconSizeX/2+20, noteY+iconSizeY/2+ICON_SIZE/2); const core::rect source(core::position2d(0,0), m_music_icon->getSize()); draw2DImage(m_music_icon, dest, source, NULL, NULL, true); } #endif } // drawGlobalMusicDescription //----------------------------------------------------------------------------- void RaceGUIBase::drawGlobalGoal() { static video::SColor color = video::SColor(255, 255, 255, 255); core::rect pos(irr_driver->getActualScreenSize().Width/2, irr_driver->getActualScreenSize().Height/2, irr_driver->getActualScreenSize().Width/2, irr_driver->getActualScreenSize().Height/2); gui::IGUIFont* font = GUIEngine::getTitleFont(); font->draw(m_string_goal.c_str(), pos, color, true, true); } // ---------------------------------------------------------------------------- /** Draws the ready-set-go message on the screen. */ void RaceGUIBase::drawGlobalReadySetGo() { // This function is called only in a relevant phase, // So we can put common elements here static video::SColor color = video::SColor(255, 255, 255, 255); gui::IGUIFont* font = GUIEngine::getTitleFont(); int x = irr_driver->getActualScreenSize().Width/2; int y = irr_driver->getActualScreenSize().Height*2/5; core::rect pos(x,y,x,y); switch (World::getWorld()->getPhase()) { case WorldStatus::WAIT_FOR_SERVER_PHASE: { font->draw(StringUtils::loadingDots( m_string_waiting_for_others.c_str()), pos, color, true, true); } break; case WorldStatus::READY_PHASE: { font->draw(m_string_ready.c_str(), pos, color, true, true); } break; case WorldStatus::SET_PHASE: { font->draw(m_string_set.c_str(), pos, color, true, true); } break; case WorldStatus::GO_PHASE: { if (race_manager->getCoinTarget() > 0) font->draw(_("Collect nitro!"), pos, color, true, true); else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER) font->draw(_("Follow the leader!"), pos, color, true, true); else font->draw(m_string_go.c_str(), pos, color, true, true); } break; default: break; } // switch } // drawGlobalReadySetGo //----------------------------------------------------------------------------- /** Draw players icons and, depending on the current mode, their time * or their score (battle lives, egg collected, etc.). */ void RaceGUIBase::drawGlobalPlayerIcons(int bottom_margin) { #ifndef SERVER_ONLY // For now, don't draw player icons when in soccer mode const RaceManager::MinorRaceModeType minor_mode = race_manager->getMinorMode(); if(minor_mode == RaceManager::MINOR_MODE_SOCCER) return; int x_base = 10; int y_base = 25; unsigned int y_space = irr_driver->getActualScreenSize().Height - bottom_margin - y_base; // Special case : when 3 players play, use 4th window to display such stuff if (race_manager->getIfEmptyScreenSpaceExists()) { irr::core::recti Last_Space = irr_driver->getSplitscreenWindow(race_manager->getNumLocalPlayers()); x_base = Last_Space.UpperLeftCorner.X; y_base = Last_Space.UpperLeftCorner.Y; y_space = irr_driver->getActualScreenSize().Height - y_base; } unsigned int sta = race_manager->getNumSpareTireKarts(); const unsigned int num_karts = race_manager->getNumberOfKarts() - sta; // -2 because that's the spacing further on int ICON_PLAYER_WIDTH = y_space / (num_karts) - 2; int icon_width_max = (int)(60*(irr_driver->getActualScreenSize().Width/1024.0f)); int icon_width_min = (int)(35*((irr_driver->getActualScreenSize().Height - (y_base+10))/720.0f)); if (icon_width_min < 35) icon_width_min = 35; if (icon_width_min > icon_width_max) { int icon_width_tmp = icon_width_max; icon_width_max = icon_width_min; icon_width_min = icon_width_tmp; } // Make sure it fits within our boundaries if (ICON_PLAYER_WIDTH > icon_width_max) ICON_PLAYER_WIDTH = icon_width_max; if (ICON_PLAYER_WIDTH < icon_width_min) ICON_PLAYER_WIDTH = icon_width_min; // Icon width for the AI karts int ICON_WIDTH = ICON_PLAYER_WIDTH * 5 / 6; WorldWithRank* world = dynamic_cast(World::getWorld()); //initialize m_previous_icons_position if(m_previous_icons_position.size()==0) { for(unsigned int i=0; igetKart(i); int position = kart->getPosition(); core::vector2d pos(x_base,y_base+(position-1)*(ICON_PLAYER_WIDTH+2)); m_previous_icons_position.push_back(pos); } } int x; int y; float previous_distance=0.0;//no need to be far ahead, first kart won't try to overlap int previous_x=x_base; int previous_y=y_base-ICON_PLAYER_WIDTH-2; gui::ScalableFont* font = GUIEngine::getFont(); const unsigned int kart_amount = world->getNumKarts() - sta; //where is the limit to hide last icons int y_icons_limit = irr_driver->getActualScreenSize().Height - bottom_margin - ICON_PLAYER_WIDTH; if (race_manager->getIfEmptyScreenSpaceExists()) { y_icons_limit = irr_driver->getActualScreenSize().Height - ICON_WIDTH; } world->getKartsDisplayInfo(&m_kart_display_infos); for(int position = 1; position <= (int)kart_amount ; position++) { AbstractKart *kart = world->getKartAtPosition(position); if (kart->getPosition() == -1)//if position is not set { //we use karts ordered by id only //(needed for beginning of battle mode) kart= world->getKart(position-1); } if(kart->isEliminated()) continue; unsigned int kart_id = kart->getWorldKartId(); KartIconDisplayInfo &info = m_kart_display_infos[kart_id]; //x,y is the target position int lap = info.lap; // In battle mode mode there is no distance along track etc. if (minor_mode==RaceManager::MINOR_MODE_3_STRIKES || minor_mode==RaceManager::MINOR_MODE_FREE_FOR_ALL || minor_mode==RaceManager::MINOR_MODE_CAPTURE_THE_FLAG || minor_mode==RaceManager::MINOR_MODE_EASTER_EGG) { x = x_base; y = previous_y+ICON_PLAYER_WIDTH+2; } else { LinearWorld *linear_world = (LinearWorld*)(World::getWorld()); float distance = linear_world->getDistanceDownTrackForKart(kart_id, true) + Track::getCurrentTrack()->getTrackLength()*lap; if ((position>1) && (previous_distance-distancehasFinishedRace()) && lap >= 0 ) { //linear translation : form (0,ICON_PLAYER_WIDTH+2) to // (previous_x-x_base+(ICON_PLAYER_WIDTH+2)/2,0) x=(int)(x_base+(1-(previous_distance-distance) /m_dist_show_overlap) *(previous_x-x_base+(ICON_PLAYER_WIDTH+2)/2)); y=(int)(previous_y+(previous_distance-distance) /m_dist_show_overlap*(ICON_PLAYER_WIDTH+2)); } else { x=x_base; y=previous_y+ICON_PLAYER_WIDTH+2; } previous_distance=distance; } // not three-strike-battle previous_x=x;//save coord of the previous kart in list previous_y=y; //soft movement using previous position: x=(int)((x+m_previous_icons_position[kart_id].X*m_icons_inertia) /(m_icons_inertia+1)); y=(int)((y+m_previous_icons_position[kart_id].Y*m_icons_inertia) /(m_icons_inertia+1)); //save position for next time m_previous_icons_position[kart_id].X=x; m_previous_icons_position[kart_id].Y=y; if (y>y_icons_limit) { //there are too many icons, write "Top 9", to express that //there is not everybody shown core::recti pos_top; pos_top.UpperLeftCorner.Y = y_base-22; pos_top.UpperLeftCorner.X = x_base; static video::SColor color = video::SColor(255, 255, 255, 255); pos_top.LowerRightCorner = pos_top.UpperLeftCorner; //I18N: When some GlobalPlayerIcons are hidden, write "Top 10" to show it font->setBlackBorder(true); font->setThinBorder(true); font->draw(_("Top %i", position-1 ), pos_top, color); font->setThinBorder(false); font->setBlackBorder(false); break; } if (info.m_text.size() > 0) { core::rect pos(x+ICON_PLAYER_WIDTH, y+5, x+ICON_PLAYER_WIDTH, y+5); if (info.m_outlined_font) { GUIEngine::getOutlineFont()->draw(info.m_text, pos, info.m_color, false, false, NULL, true/*ignore RTL*/); } else { font->setBlackBorder(true); font->setThinBorder(true); font->draw(info.m_text, pos, info.m_color, false, false, NULL, true/*ignore RTL*/); font->setThinBorder(false); font->setBlackBorder(false); } } if (info.special_title.size() > 0) { core::rect pos(x+ICON_PLAYER_WIDTH, y+5, x+ICON_PLAYER_WIDTH, y+5); core::stringw s(info.special_title.c_str()); font->setBlackBorder(true); font->setThinBorder(true); font->draw(s.c_str(), pos, info.m_color, false, false, NULL, true /* ignore RTL */); font->setThinBorder(false); font->setBlackBorder(false); } int w = kart->getController() ->isLocalPlayerController() ? ICON_PLAYER_WIDTH : ICON_WIDTH; drawPlayerIcon(kart, x, y, w); } //next position #endif } // drawGlobalPlayerIcons //----------------------------------------------------------------------------- /** Draw one player icon * Takes care of icon looking different due to plumber, squashing, ... */ void RaceGUIBase::drawPlayerIcon(AbstractKart *kart, int x, int y, int w) { #ifndef SERVER_ONLY video::ITexture *icon = kart->getKartProperties()->getIconMaterial()->getTexture(); CaptureTheFlag* ctf = dynamic_cast(World::getWorld()); unsigned int kart_id = kart->getWorldKartId(); // CTF if (ctf) { if (ctf->getRedHolder() == (int)kart_id) { video::ITexture* red = irr_driver->getTexture(FileManager::GUI_ICON, "red_flag.png"); const core::rect rect(core::position2d(0, 0), red->getSize()); const core::rect pos1 (x - 20, y - 10, x + w - 20, y + w - 30); draw2DImage(red, pos1, rect, NULL, NULL, true); } else if (ctf->getBlueHolder() == (int)kart_id) { video::ITexture* blue = irr_driver->getTexture(FileManager::GUI_ICON, "blue_flag.png"); const core::rect rect(core::position2d(0, 0), blue->getSize()); const core::rect pos1 (x - 20, y - 10, x + w - 20, y + w - 30); draw2DImage(blue, pos1, rect, NULL, NULL, true); } } const core::rect pos(x, y, x+w, y+w); //to bring to light the player's icon: add a background if (kart->getController()->isLocalPlayerController() && m_icons_frame != NULL) { video::SColor colors[4]; for (unsigned int i=0;i<4;i++) { colors[i]=kart->getKartProperties()->getColor(); colors[i].setAlpha( 100+(int)(100*cos(M_PI/2*i+World::getWorld()->getTime()*2))); } const core::rect rect(core::position2d(0,0), m_icons_frame->getSize()); draw2DImage(m_icons_frame, pos, rect,NULL, colors, true); } // Fixes crash bug, why are certain icons not showing up? if (icon && !kart->getKartAnimation() && !kart->isSquashed()) { const core::rect rect(core::position2d(0,0), icon->getSize()); draw2DImage(icon, pos, rect, NULL, NULL, true, kart->isGhostKart()); } //draw status info - icon fade out in case of rescue/explode if (icon && dynamic_cast(kart->getKartAnimation())) { //icon fades to the left float t = kart->getKartAnimation()->getAnimationTimer(); float t_anim=100*sin(0.5f*M_PI*t); const core::rect rect1(core::position2d(0,0), icon->getSize()); const core::rect pos1((int)(x-t_anim), y, (int)(x+w-t_anim), y+w); draw2DImage(icon, pos1, rect1, NULL, NULL, true); } if (icon && !kart->getKartAnimation() && kart->isSquashed() ) { //syncs icon squash with kart squash const core::rect destRect(core::position2d(x,y+w/4), core::position2d(x+w,y+w*3/4)); const core::rect sourceRect(core::position2d(0,0), icon->getSize()); draw2DImage(icon, destRect, sourceRect, NULL, NULL, true); } if (icon && dynamic_cast(kart->getKartAnimation()) ) { //exploses into 4 parts float t = kart->getKartAnimation()->getAnimationTimer(); float t_anim=50.0f*sin(0.5f*M_PI*t); u16 icon_size_x=icon->getSize().Width; u16 icon_size_y=icon->getSize().Height; const core::rect rect1(0, 0, icon_size_x/2,icon_size_y/2); const core::rect pos1((int)(x-t_anim), (int)(y-t_anim), (int)(x+w/2-t_anim), (int)(y+w/2-t_anim)); draw2DImage(icon, pos1, rect1, NULL, NULL, true); const core::rect rect2(icon_size_x/2,0, icon_size_x,icon_size_y/2); const core::rect pos2((int)(x+w/2+t_anim), (int)(y-t_anim), (int)(x+w+t_anim), (int)(y+w/2-t_anim)); draw2DImage(icon, pos2, rect2, NULL, NULL, true); const core::rect rect3(0, icon_size_y/2, icon_size_x/2,icon_size_y); const core::rect pos3((int)(x-t_anim), (int)(y+w/2+t_anim), (int)(x+w/2-t_anim), (int)(y+w+t_anim)); draw2DImage(icon, pos3, rect3, NULL, NULL, true); const core::rect rect4(icon_size_x/2,icon_size_y/2,icon_size_x,icon_size_y); const core::rect pos4((int)(x+w/2+t_anim), (int)(y+w/2+t_anim), (int)(x+w+t_anim), (int)(y+w+t_anim)); draw2DImage(icon, pos4, rect4, NULL, NULL, true); } //Plunger if (kart->getBlockedByPlungerTicks()>0) { video::ITexture *icon_plunger = powerup_manager->getIcon(PowerupManager::POWERUP_PLUNGER)->getTexture(); if (icon_plunger != NULL) { const core::rect rect(core::position2d(0,0), icon_plunger->getSize()); const core::rect pos1(x+10, y-10, x+w+10, y+w-10); draw2DImage(icon_plunger, pos1, rect, NULL, NULL, true); } } //attachment if (kart->getAttachment()->getType() != Attachment::ATTACH_NOTHING) { video::ITexture *icon_attachment = attachment_manager->getIcon(kart->getAttachment()->getType()) ->getTexture(); if (icon_attachment != NULL) { const core::rect rect(core::position2d(0,0), icon_attachment->getSize()); const core::rect pos1(x-20, y-10, x+w-20, y+w-10); draw2DImage(icon_attachment, pos1, rect, NULL, NULL, true); } } //lap flag for finished karts if (kart->hasFinishedRace()) { if (m_lap_flag != NULL) { const core::rect rect(core::position2d(0, 0), m_lap_flag->getSize()); const core::rect pos1(x - 20, y - 10, x + w - 20, y + w - 10); draw2DImage(m_lap_flag, pos1, rect, NULL, NULL, true); } } #endif } // drawPlayerIcon // ---------------------------------------------------------------------------- /** Draws the plunger-in-face if necessary. Does nothing if there is no * plunger in face atm. */ void RaceGUIBase::drawPlungerInFace(const Camera *camera, float dt) { #ifndef SERVER_ONLY const AbstractKart *kart = camera->getKart(); if (kart->getBlockedByPlungerTicks()<=0) { m_plunger_state = PLUNGER_STATE_INIT; return; } const core::recti &viewport = camera->getViewport(); const int screen_width = viewport.LowerRightCorner.X - viewport.UpperLeftCorner.X; if(m_plunger_state == PLUNGER_STATE_INIT) { m_plunger_move_time = 0.0f; m_plunger_offset = core::vector2di(0,0); m_plunger_state = PLUNGER_STATE_SLOW_2; m_plunger_speed = core::vector2df(0, 0); } if(World::getWorld()->getPhase()!=World::IN_GAME_MENU_PHASE) { m_plunger_move_time -= dt; if(m_plunger_move_time < dt && m_plunger_state!=PLUNGER_STATE_FAST) { const float fast_time = 0.3f; if(kart->getBlockedByPlungerTicks()time2Ticks(fast_time)) { // First time we reach faste state: select random target point // at top of screen and set speed accordingly RandomGenerator random; float movement_fraction = 0.3f; int plunger_x_target = screen_width/2 + random.get((int)(screen_width*movement_fraction)) - (int)(screen_width*movement_fraction*0.5f); m_plunger_state = PLUNGER_STATE_FAST; m_plunger_speed = core::vector2df((plunger_x_target-screen_width/2)/fast_time, viewport.getHeight()*0.5f/fast_time); m_plunger_move_time = fast_time; } else { RandomGenerator random; m_plunger_move_time = 0.1f+random.get(50)/200.0f; // Plunger is either moving or not moving if(m_plunger_state==PLUNGER_STATE_SLOW_1) { m_plunger_state = PLUNGER_STATE_SLOW_2; m_plunger_speed = core::vector2df(0, 0.05f*viewport.getHeight() /m_plunger_move_time ); } else { m_plunger_state = PLUNGER_STATE_SLOW_1; m_plunger_speed = core::vector2df(0, 0.02f*viewport.getHeight() /m_plunger_move_time ); } } // has not reach fast moving state } m_plunger_offset.X += (int)(m_plunger_speed.X * dt); m_plunger_offset.Y += (int)(m_plunger_speed.Y * dt); } if (m_plunger_face != NULL) { const int plunger_size = (int)(0.6f * screen_width); int offset_y = viewport.UpperLeftCorner.Y + viewport.getHeight()/2 - plunger_size/2 - m_plunger_offset.Y; int plunger_x = viewport.UpperLeftCorner.X + screen_width/2 - plunger_size/2; plunger_x += (int)m_plunger_offset.X; core::rect dest(plunger_x, offset_y, plunger_x+plunger_size, offset_y+plunger_size); const core::rect source(core::position2d(0,0), m_plunger_face->getSize()); draw2DImage(m_plunger_face, dest, source, &viewport /* clip */, NULL /* color */, true /* alpha */ ); } #endif // !SERVER_ONLY } // drawPlungerInFace // ---------------------------------------------------------------------------- void RaceGUIBase::removeReferee() { if (m_referee->isAttached()) // race phase: { m_referee->removeFromSceneGraph(); } } // removeReferee