/** Called at the end of a race. Updates highscores, pauses the game, and * informs the unlock manager about the finished race. This function must * be called after all other stats were updated from the different game * modes. */ void World::terminateRace() { m_schedule_pause = false; m_schedule_unpause = false; // Update the estimated finishing time for all karts that haven't // finished yet. const unsigned int kart_amount = getNumKarts(); for(unsigned int i = 0; i < kart_amount ; i++) { if(!m_karts[i]->hasFinishedRace() && !m_karts[i]->isEliminated()) { m_karts[i]->finishedRace(estimateFinishTimeForKart(m_karts[i])); } } // i<kart_amount // Update highscores, and retrieve the best highscore if relevant // to show it in the GUI int best_highscore_rank = -1; int best_finish_time = -1; std::string highscore_who = ""; StateManager::ActivePlayer* best_player = NULL; updateHighscores(&best_highscore_rank, &best_finish_time, &highscore_who, &best_player); unlock_manager->getCurrentSlot()->raceFinished(); if (m_race_gui) m_race_gui->clearAllMessages(); // we can't delete the race gui here, since it is needed in case of // a restart: the constructor of it creates some textures which assume // that no scene nodes exist. In case of a restart there are scene nodes, // so we can't create the race gui again, so we keep it around // and save the pointer. assert(m_saved_race_gui==NULL); m_saved_race_gui = m_race_gui; RaceResultGUI* results = RaceResultGUI::getInstance(); m_race_gui = results; if (best_highscore_rank > 0) { results->setHighscore(highscore_who, best_player, best_highscore_rank, best_finish_time); } else { results->clearHighscores(); } StateManager::get()->pushScreen(results); WorldStatus::terminateRace(); } // terminateRace
/** Ends the race early and places still active player karts at the back. * The race immediately goes to the result stage, estimating the time for the * karts still in the race. Still active player karts get a penalty in time * as well as being placed at the back. Players that already finished keep * their position. * * End time for the punished players is calculated as follows * end_time = current_time + (estimated_time - current_time) * + (estimated_time_for_last - current_time) * = estimated_time + estimated_time_for_last - current_time * This will put them at the end at all times. The further you (and the last in * the race) are from the finish line, the harsher the punishment will be. */ void StandardRace::endRaceEarly() { const unsigned int kart_amount = (unsigned int)m_karts.size(); std::vector<int> active_players; // Required for debugging purposes beginSetKartPositions(); for (unsigned int i = 1; i <= kart_amount; i++) { int kartid = m_position_index[i-1]; AbstractKart* kart = m_karts[kartid]; if (kart->hasFinishedRace()) { // Have to do this to keep endSetKartPosition happy setKartPosition(kartid, kart->getPosition()); continue; } if (kart->getController()->isPlayerController()) { // Keep active players apart for now active_players.push_back(kartid); } else { // AI karts finish setKartPosition(kartid, i - (unsigned int) active_players.size()); kart->finishedRace(estimateFinishTimeForKart(kart)); } } // i <= kart_amount // Now make the active players finish for (unsigned int i = 0; i < active_players.size(); i++) { int kartid = active_players[i]; int position = getNumKarts() - (int) active_players.size() + 1 + i; setKartPosition(kartid, position); m_karts[kartid]->eliminate(); } // Finish the active players endSetKartPositions(); setPhase(RESULT_DISPLAY_PHASE); if (!isNetworkWorld() || NetworkConfig::get()->isServer()) terminateRace(); } // endRaceEarly
/** Called at the end of a race. Updates highscores, pauses the game, and * informs the unlock manager about the finished race. This function must * be called after all other stats were updated from the different game * modes. */ void World::terminateRace() { m_schedule_pause = false; m_schedule_unpause = false; // Update the estimated finishing time for all karts that haven't // finished yet. const unsigned int kart_amount = getNumKarts(); for(unsigned int i = 0; i < kart_amount ; i++) { if(!m_karts[i]->hasFinishedRace() && !m_karts[i]->isEliminated()) { m_karts[i]->finishedRace(estimateFinishTimeForKart(m_karts[i])); } } // i<kart_amount // Update highscores, and retrieve the best highscore if relevant // to show it in the GUI int best_highscore_rank = -1; int best_finish_time = -1; std::string highscore_who = ""; StateManager::ActivePlayer* best_player = NULL; if (!this->isNetworkWorld()) { updateHighscores(&best_highscore_rank, &best_finish_time, &highscore_who, &best_player); } // Check achievements PlayerManager::increaseAchievement(AchievementInfo::ACHIEVE_COLUMBUS, getTrack()->getIdent(), 1); if (raceHasLaps()) { PlayerManager::increaseAchievement(AchievementInfo::ACHIEVE_MARATHONER, "laps", race_manager->getNumLaps()); } Achievement *achiev = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_GOLD_DRIVER); if (achiev) { std::string mode_name = getIdent(); // Get the race mode name int winner_position = 1; unsigned int opponents = achiev->getInfo()->getGoalValue("opponents"); // Get the required opponents number if (mode_name == IDENT_FTL) { winner_position = 2; opponents++; } for(unsigned int i = 0; i < kart_amount; i++) { // Retrieve the current player StateManager::ActivePlayer* p = m_karts[i]->getController()->getPlayer(); if (p && p->getConstProfile() == PlayerManager::getCurrentPlayer()) { // Check if the player has won if (m_karts[i]->getPosition() == winner_position && kart_amount > opponents ) { // Update the achievement mode_name = StringUtils::toLowerCase(mode_name); if (achiev->getValue("opponents") <= 0) PlayerManager::increaseAchievement(AchievementInfo::ACHIEVE_GOLD_DRIVER, "opponents", opponents); PlayerManager::increaseAchievement(AchievementInfo::ACHIEVE_GOLD_DRIVER, mode_name, 1); } } } // for i < kart_amount } // if (achiev) Achievement *win = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_UNSTOPPABLE); //if achivement has been unlocked if (win->getValue("wins") < 5 ) { for(unsigned int i = 0; i < kart_amount; i++) { // Retrieve the current player StateManager::ActivePlayer* p = m_karts[i]->getController()->getPlayer(); if (p && p->getConstProfile() == PlayerManager::getCurrentPlayer()) { // Check if the player has won if (m_karts[i]->getPosition() == 1 ) { // Increase number of consecutive wins PlayerManager::increaseAchievement(AchievementInfo::ACHIEVE_UNSTOPPABLE, "wins", 1); } else { //Set number of consecutive wins to 0 win->reset(); } } } } PlayerManager::getCurrentPlayer()->raceFinished(); if (m_race_gui) m_race_gui->clearAllMessages(); // we can't delete the race gui here, since it is needed in case of // a restart: the constructor of it creates some textures which assume // that no scene nodes exist. In case of a restart there are scene nodes, // so we can't create the race gui again, so we keep it around // and save the pointer. assert(m_saved_race_gui==NULL); m_saved_race_gui = m_race_gui; RaceResultGUI* results = RaceResultGUI::getInstance(); m_race_gui = results; if (best_highscore_rank > 0) { results->setHighscore(highscore_who, best_player, best_highscore_rank, best_finish_time); } else { results->clearHighscores(); } results->push(); WorldStatus::terminateRace(); } // terminateRace
/** General update function called once per frame. This updates the kart * sectors, which are then used to determine the kart positions. * \param dt Time step size. */ void LinearWorld::update(float dt) { // run generic parent stuff that applies to all modes. It // especially updates the kart positions. WorldWithRank::update(dt); if (m_last_lap_sfx_playing && m_last_lap_sfx->getStatus() != SFXBase::SFX_PLAYING) { music_manager->resetTemporaryVolume(); m_last_lap_sfx_playing = false; } const unsigned int kart_amount = getNumKarts(); // Do stuff specific to this subtype of race. // ------------------------------------------ for(unsigned int n=0; n<kart_amount; n++) { KartInfo& kart_info = m_kart_info[n]; AbstractKart* kart = m_karts[n]; // Nothing to do for karts that are currently being // rescued or eliminated if(kart->getKartAnimation()) continue; kart_info.getTrackSector()->update(kart->getFrontXYZ()); kart_info.m_overall_distance = kart_info.m_race_lap * m_track->getTrackLength() + getDistanceDownTrackForKart(kart->getWorldKartId()); } // for n // Update all positions. This must be done after _all_ karts have // updated their position and laps etc, otherwise inconsistencies // (like two karts at same position) can occur. // --------------------------------------------------------------- WorldWithRank::updateTrack(dt); updateRacePosition(); for (unsigned int i=0; i<kart_amount; i++) { // ---------- update rank ------ if (m_karts[i]->hasFinishedRace() || m_karts[i]->isEliminated() ) continue; // During the last lap update the estimated finish time. // This is used to play the faster music, and by the AI if (m_kart_info[i].m_race_lap == race_manager->getNumLaps()-1) { m_kart_info[i].m_estimated_finish = estimateFinishTimeForKart(m_karts[i]); } checkForWrongDirection(i, dt); } #ifdef DEBUG // Debug output in case that the double position error occurs again. std::vector<int> pos_used; pos_used.resize(kart_amount+1, -99); for(unsigned int i=0; i<kart_amount; i++) { if(pos_used[m_karts[i]->getPosition()]!=-99) { for(unsigned int j=0; j<kart_amount; j++) { Log::verbose("[LinearWorld]", "kart id=%u, position=%d, finished=%d, laps=%d, " "distanceDownTrack=%f overallDistance=%f %s", j, m_karts[j]->getPosition(), m_karts[j]->hasFinishedRace(), m_kart_info[j].m_race_lap, getDistanceDownTrackForKart(m_karts[j]->getWorldKartId()), m_kart_info[j].m_overall_distance, (m_karts[j]->getPosition() == m_karts[i]->getPosition() ? "<--- !!!" : "") ); } } pos_used[m_karts[i]->getPosition()]=i; } #endif } // update
/** This function is called when the race is finished, but end-of-race * animations have still to be played. In the case of profiling, * we can just abort here without waiting for the animations. */ void ProfileWorld::enterRaceOverState() { // If in timing mode, the number of laps is way too high (which avoids // aborting too early). So in this case determine the maximum number // of laps and set this +1 as the number of laps to get more meaningful // time estimations. if(m_profile_mode==PROFILE_TIME) { int max_laps = -2; for(unsigned int i=0; i<race_manager->getNumberOfKarts(); i++) { if(m_kart_info[i].m_race_lap>max_laps) max_laps = m_kart_info[i].m_race_lap; } // for i<getNumberOfKarts race_manager->setNumLaps(max_laps+1); } StandardRace::enterRaceOverState(); // Estimate finish time and set all karts to be finished. for (unsigned int i=0; i<race_manager->getNumberOfKarts(); i++) { // ---------- update rank ------ if (m_karts[i]->hasFinishedRace() || m_karts[i]->isEliminated()) continue; m_karts[i]->finishedRace(estimateFinishTimeForKart(m_karts[i])); } // Print framerate statistics float runtime = (irr_driver->getRealTime()-m_start_time)*0.001f; Log::verbose("profile", "Number of frames: %d time %f, Average FPS: %f", m_frame_count, runtime, (float)m_frame_count/runtime); // Print geometry statistics if we're not in no-graphics mode if(!m_no_graphics) { Log::verbose("profile", "Average # drawn nodes %f k", (float)m_num_triangles/m_frame_count); Log::verbose("profile", "Average # culled nodes: %f k", (float)m_num_culls/m_frame_count); Log::verbose("profile", "Average # solid nodes: %f k", (float)m_num_solid/m_frame_count); Log::verbose("profile", "Average # transparent nodes: %f", (float)m_num_transparent/m_frame_count); Log::verbose("profile", "Average # transp. effect nodes: %f", (float)m_num_trans_effect/m_frame_count); } // Print race statistics for each individual kart float min_t=999999.9f, max_t=0.0, av_t=0.0; Log::verbose("profile", "name start_position end_position time average_speed top_speed " "skid_time rescue_time rescue_count brake_count " "explosion_time explosion_count bonus_count banana_count " "small_nitro_count large_nitro_count bubblegum_count"); std::set<std::string> all_groups; for ( KartList::size_type i = 0; i < m_karts.size(); ++i) { KartWithStats* kart = dynamic_cast<KartWithStats*>(m_karts[i]); max_t = std::max(max_t, kart->getFinishTime()); min_t = std::min(min_t, kart->getFinishTime()); av_t += kart->getFinishTime(); std::ostringstream ss; ss << kart->getIdent() << " " << kart->getController()->getControllerName() << " "; ss << 1+(int)i << " " << kart->getPosition() << " " << kart->getFinishTime() << " "; all_groups.insert(kart->getController()->getControllerName()); float distance = (float)(m_profile_mode==PROFILE_LAPS ? race_manager->getNumLaps() : 1); distance *= m_track->getTrackLength(); ss << distance/kart->getFinishTime() << " " << kart->getTopSpeed() << " "; ss << kart->getSkiddingTime() << " " << kart->getRescueTime() << " "; ss << kart->getRescueCount() << " " << kart->getBrakeCount() << " "; ss << kart->getExplosionTime() << " " << kart->getExplosionCount() << " "; ss << kart->getBonusCount() << " " << kart->getBananaCount() << " "; ss << kart->getSmallNitroCount() << " " << kart->getLargeNitroCount() << " "; ss << kart->getBubblegumCount() << " " << kart->getOffTrackCount() << " "; Log::verbose("profile", ss.str().c_str()); } // Print group statistics of all karts Log::verbose("profile", "min %f max %f av %f\n", min_t, max_t, av_t/m_karts.size()); // Determine maximum length of group name unsigned int max_len=4; // for 'name' heading for(std::set<std::string>::iterator it = all_groups.begin(); it !=all_groups.end(); it++) { if(it->size()>max_len) max_len = (unsigned int) it->size(); } max_len++; // increase by 1 for one additional space after the name std::ostringstream ss; Log::verbose("profile", ""); ss << "name" << std::setw(max_len-4) << " " << "Strt End Time AvSp Top Skid Resc Rsc Brake Expl Exp Itm Ban SNitLNit Bub Off"; Log::verbose("profile", ss.str().c_str()); for(std::set<std::string>::iterator it = all_groups.begin(); it !=all_groups.end(); it++) { int count = 0, position_gain = 0, rescue_count = 0; int brake_count = 0, bonus_count = 0, banana_count = 0; int l_nitro_count = 0, s_nitro_count = 0, bubble_count = 0; int expl_count = 0, off_track_count = 0; float skidding_time = 0.0f, rescue_time = 0.0f, expl_time = 0.0f; float av_time = 0.0f; for ( unsigned int i = 0; i < (unsigned int)m_karts.size(); ++i) { KartWithStats* kart = dynamic_cast<KartWithStats*>(m_karts[i]); const std::string &name=kart->getController()->getControllerName(); if(name!=*it) continue; count ++; std::ostringstream ss; ss.setf(std::ios::fixed, std::ios::floatfield); ss.precision(2); ss << name << std::setw(max_len-name.size()) << " "; ss << std::setw(4) << 1 + i << std::setw(4) << kart->getPosition() << std::setw(7) << kart->getFinishTime(); position_gain += 1+i - kart->getPosition(); float distance = (float)(m_profile_mode==PROFILE_LAPS ? race_manager->getNumLaps() : 1); distance *= m_track->getTrackLength(); Log::verbose("profile", "%s %4.2f %3.2f %6.2f %4.2f %3d %5d %4.2f %3d %3d %3d %3d %3d %3d %5d", ss.str().c_str(), distance/kart->getFinishTime(), kart->getTopSpeed(), kart->getSkiddingTime(), kart->getRescueTime(), kart->getRescueCount(), kart->getBrakeCount(), kart->getExplosionTime(), kart->getExplosionCount(), kart->getBonusCount(), kart->getBananaCount(), kart->getSmallNitroCount(), kart->getLargeNitroCount(), kart->getBubblegumCount(), kart->getOffTrackCount() ); Log::verbose("profile", "nitro %f\n", kart->getEnergy()); av_time += kart->getFinishTime(); skidding_time += kart->getSkiddingTime(); rescue_time += kart->getRescueTime(); rescue_count += kart->getRescueCount(); brake_count += kart->getBrakeCount(); bonus_count += kart->getBonusCount(); banana_count += kart->getBananaCount(); l_nitro_count += kart->getLargeNitroCount(); s_nitro_count += kart->getSmallNitroCount(); bubble_count += kart->getBubblegumCount(); expl_time += kart->getExplosionTime(); expl_count += kart->getExplosionCount(); off_track_count += kart->getOffTrackCount(); } // for i < m_karts.size Log::verbose("profile", std::string(max_len+85, '-').c_str()); ss.clear(); ss.str(""); ss << *it << std::string(max_len-it->size(),' '); ss << std::showpos << std::setw(4) << position_gain << std::noshowpos << std::setw(13) << av_time/count << std::string(11,' '); Log::verbose("profile", "%s%6.2f %4.2f %3d %5d %4.2f %3d %3d %3d %3d %3d %3d %5d", ss.str().c_str(), skidding_time/count, rescue_time/count, rescue_count,brake_count, expl_time, expl_count, bonus_count, banana_count, s_nitro_count, l_nitro_count, bubble_count, off_track_count); Log::verbose("profile", ""); } // for it !=all_groups.end delete this; main_loop->abort(); } // enterRaceOverState
/** General update function called once per frame. This updates the kart * sectors, which are then used to determine the kart positions. * \param dt Time step size. */ void LinearWorld::update(float delta) { // run generic parent stuff that applies to all modes. It // especially updates the kart positions. WorldWithRank::update(delta); if (m_last_lap_sfx_playing && m_last_lap_sfx->getStatus() != SFXManager::SFX_PLAYING) { music_manager->getCurrentMusic()->resetTemporaryVolume(); m_last_lap_sfx_playing = false; } const unsigned int kart_amount = getNumKarts(); // Do stuff specific to this subtype of race. // ------------------------------------------ for(unsigned int n=0; n<kart_amount; n++) { KartInfo& kart_info = m_kart_info[n]; Kart* kart = m_karts[n]; // Nothing to do for karts that are currently being rescued or eliminated if(kart->playingEmergencyAnimation()) continue; // ---------- deal with sector data --------- // update sector variables int prev_sector = kart_info.m_track_sector; m_track->getQuadGraph().findRoadSector(kart->getXYZ(), &kart_info.m_track_sector); kart_info.m_on_road = kart_info.m_track_sector != QuadGraph::UNKNOWN_SECTOR; if(kart_info.m_on_road) { kart_info.m_last_valid_sector = kart_info.m_track_sector; kart_info.m_last_valid_race_lap = kart_info.m_race_lap; } else { // Kart off road. Find the closest sector instead. kart_info.m_track_sector = m_track->getQuadGraph().findOutOfRoadSector(kart->getXYZ(), prev_sector ); } // Update track coords (=progression) m_track->getQuadGraph().spatialToTrack(&kart_info.m_curr_track_coords, kart->getXYZ(), kart_info.m_track_sector ); } // for n // Update all positions. This must be done after _all_ karts have // updated their position and laps etc, otherwise inconsistencies // (like two karts at same position) can occur. // --------------------------------------------------------------- updateRacePosition(); for (unsigned int i=0; i<kart_amount; i++) { // ---------- update rank ------ if (m_karts[i]->hasFinishedRace() || m_karts[i]->isEliminated()) continue; // During the last lap update the estimated finish time. // This is used to play the faster music, and by the AI if (m_kart_info[i].m_race_lap == race_manager->getNumLaps()-1) { m_kart_info[i].m_estimated_finish = estimateFinishTimeForKart(m_karts[i]); } checkForWrongDirection(i); } #ifdef DEBUG // FIXME: Debug output in case that the double position error occurs again. std::vector<int> pos_used; pos_used.resize(kart_amount+1, -99); for(unsigned int i=0; i<kart_amount; i++) { if(pos_used[m_karts[i]->getPosition()]!=-99) { for(unsigned int j =0; j<kart_amount; j++) { printf("kart id=%d, position=%d, finished=%d, laps=%d, distanceDownTrack=%f %s\n", j, m_karts[j]->getPosition(), m_karts[j]->hasFinishedRace(), m_kart_info[j].m_race_lap, getDistanceDownTrackForKart(m_karts[j]->getWorldKartId()), (m_karts[j]->getPosition() == m_karts[i]->getPosition() ? "<--- !!!" : "")); } } pos_used[m_karts[i]->getPosition()]=i; } #endif } // update