/** Called before a race is started (or restarted). It resets the data * structures that keep track of position and distance long track of * all karts. */ void LinearWorld::reset() { WorldWithRank::reset(); m_last_lap_sfx_played = false; m_last_lap_sfx_playing = false; const unsigned int kart_amount = (unsigned int) m_karts.size(); for(unsigned int i=0; i<kart_amount; i++) { m_kart_info[i].reset(); m_karts[i]->setWrongwayCounter(0); } // next kart // At the moment the last kart would be the one that is furthest away // from the start line, i.e. it would determine the amount by which // the track length must be extended (to avoid negative numbers in // estimateFinishTimeForKart()). On the other hand future game modes // might not follow this rule, so check the distance for all karts here: m_distance_increase = m_track->getTrackLength(); for(unsigned int i=0; i<kart_amount; i++) { m_distance_increase = std::min(m_distance_increase, getDistanceDownTrackForKart(i)); } // Track length - minimum distance is how much the track length must // be increased to avoid negative values in estimateFinishTimeForKart // Increase this value somewhat in case that a kart drivess/slides // backwards a little bit at start. m_distance_increase = m_track->getTrackLength() - m_distance_increase + 5.0f; if(m_distance_increase<0) m_distance_increase = 1.0f; // shouldn't happen // First all kart infos must be updated before the kart position can be // recomputed, since otherwise 'new' (initialised) valued will be compared // with old values. updateRacePosition(); #ifdef DEBUG //FIXME: this could be defined somewhere in a central header so it can // be used everywhere #define assertExpr( ARG1, OP, ARG2 ) if (!(ARG1 OP ARG2)) \ { \ std::cerr << "Failed assert " << #ARG1 << #OP << #ARG2 << " @ " \ << __FILE__ << ":" << __LINE__ \ << "; values are (" << ARG1 << #OP << ARG2 << ")\n"; \ assert(false); \ } for (unsigned int i=0; i<kart_amount; i++) { for (unsigned int j=i+1; j<kart_amount; j++) { assertExpr( m_karts[i]->getPosition(), !=, m_karts[j]->getPosition() ); } } #endif } // reset
/** Estimate the arrival time of any karts that haven't arrived yet by using * their average speed up to now and the distance still to race. This * approach guarantees that the order of the karts won't change anymore * (karts ahead will have a higher average speed and therefore finish the * race earlier than karts further behind), so the position doesn't have to * be updated to get the correct scoring. * \param kart The kart for which to estimate the finishing times. */ float LinearWorld::estimateFinishTimeForKart(Kart* kart) { const KartInfo &kart_info = m_kart_info[kart->getWorldKartId()]; float distance_covered = kart_info.m_race_lap * m_track->getTrackLength() + getDistanceDownTrackForKart(kart->getWorldKartId()); // In case that a kart is rescued behind start line, or ... if(distance_covered<0) distance_covered =1.0f; const float full_distance = race_manager->getNumLaps()*m_track->getTrackLength(); const float average_speed = distance_covered/getTime(); // Finish time is the time needed for the whole race with // the average speed computed above. return getTime() + (full_distance - distance_covered) / average_speed; } // estimateFinishTimeForKart
/** Is called by check structures if a kart starts a new lap. * \param kart_index Index of the kart. */ void LinearWorld::newLap(unsigned int kart_index) { KartInfo &kart_info = m_kart_info[kart_index]; AbstractKart *kart = m_karts[kart_index]; // Reset reset-after-lap achievements StateManager::ActivePlayer *c = kart->getController()->getPlayer(); PlayerProfile *p = PlayerManager::getCurrentPlayer(); if (c && c->getConstProfile() == p) { p->getAchievementsStatus()->onLapEnd(); } // Only update the kart controller if a kart that has already finished // the race crosses the start line again. This avoids 'fastest lap' // messages if the end controller does a fastest lap, but especially // allows the end controller to switch end cameras if(kart->hasFinishedRace()) { kart->getController()->newLap(kart_info.m_race_lap); return; } const int lap_count = race_manager->getNumLaps(); // Only increase the lap counter and set the new time if the // kart hasn't already finished the race (otherwise the race_gui // will begin another countdown). if(kart_info.m_race_lap+1 <= lap_count) { assert(kart->getWorldKartId()==kart_index); kart_info.m_time_at_last_lap=getTime(); kart_info.m_race_lap++; m_kart_info[kart_index].m_overall_distance = m_kart_info[kart_index].m_race_lap * m_track->getTrackLength() + getDistanceDownTrackForKart(kart->getWorldKartId()); } // Last lap message (kart_index's assert in previous block already) if (raceHasLaps() && kart_info.m_race_lap+1 == lap_count) { m_race_gui->addMessage(_("Final lap!"), kart, 3.0f, video::SColor(255, 210, 100, 50), true); if(!m_last_lap_sfx_played && lap_count > 1) { if (UserConfigParams::m_music) { m_last_lap_sfx->play(); m_last_lap_sfx_played = true; m_last_lap_sfx_playing = true; // In case that no music is defined if(music_manager->getCurrentMusic() && music_manager->getMasterMusicVolume() > 0.2f) { music_manager->setTemporaryVolume(0.2f); } } else { m_last_lap_sfx_played = true; m_last_lap_sfx_playing = false; } } } else if (raceHasLaps() && kart_info.m_race_lap > 0 && kart_info.m_race_lap+1 < lap_count) { m_race_gui->addMessage(_("Lap %i", kart_info.m_race_lap+1), kart, 3.0f, video::SColor(255, 210, 100, 50), true); } // The race positions must be updated here: consider the situation where // the first kart does not cross the finish line in its last lap, instead // it passes it, the kart reverses and crosses the finishing line // backwards. Just before crossing the finishing line the kart will be on // the last lap, but with a distance along the track close to zero. // Therefore its position will be wrong. If the race position gets updated // after increasing the number of laps (but before tagging the kart to have // finished the race) the position will be correct (since the kart now // has one additional lap it will be ahead of the other karts). // Without this call the incorrect position for this kart would remain // (since a kart that has finished the race does not get its position // changed anymore), potentially resulting in a duplicated race position // (since the first kart does not have position 1, no other kart can get // position 1, so one rank will be duplicated). // Similarly the situation can happen if the distance along track should // go back to zero before actually crossing the finishing line. While this // should not happen, it could potentially be caused by floating point // errors. In this case the call to updateRacePosition will avoid // duplicated race positions as well. updateRacePosition(); // Race finished if(kart_info.m_race_lap >= race_manager->getNumLaps() && raceHasLaps()) { kart->finishedRace(getTime()); } float time_per_lap; if (kart_info.m_race_lap == 1) // just completed first lap { time_per_lap=getTime(); } else //completing subsequent laps { time_per_lap=getTime() - kart_info.m_lap_start_time; } // if new fastest lap if(time_per_lap < m_fastest_lap && raceHasLaps() && kart_info.m_race_lap>0) { m_fastest_lap = time_per_lap; std::string s = StringUtils::timeToString(time_per_lap); // Store the temporary string because clang would mess this up // (remove the stringw before the wchar_t* is used). const core::stringw &kart_name = kart->getName(); //I18N: as in "fastest lap: 60 seconds by Wilber" irr::core::stringw m_fastest_lap_message = _C("fastest_lap", "%s by %s", s.c_str(), kart_name); m_race_gui->addMessage(m_fastest_lap_message, NULL, 3.0f, video::SColor(255, 255, 255, 255), false); m_race_gui->addMessage(_("New fastest lap"), NULL, 3.0f, video::SColor(255, 255, 255, 255), false); } // end if new fastest lap kart_info.m_lap_start_time = getTime(); kart->getController()->newLap(kart_info.m_race_lap); } // newLap
/** 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
/** Find the position (rank) of every kart */ void LinearWorld::updateRacePosition() { // Mostly for debugging: beginSetKartPositions(); const unsigned int kart_amount = m_karts.size(); #ifdef DEBUG bool rank_changed = false; #endif // NOTE: if you do any changes to this loop, the next loop (see // DEBUG_KART_RANK below) needs to have the same changes applied // so that debug output is still correct!!!!!!!!!!! for (unsigned int i=0; i<kart_amount; i++) { Kart* kart = m_karts[i]; // Karts that are either eliminated or have finished the // race already have their (final) position assigned. If // these karts would get their rank updated, it could happen // that a kart that finished first will be overtaken after // crossing the finishing line and become second! if(kart->isEliminated() || kart->hasFinishedRace()) { // This is only necessary to support debugging inconsistencies // in kart position parameters. setKartPosition(i, kart->getPosition()); continue; } KartInfo& kart_info = m_kart_info[i]; int p = 1 ; const int my_id = kart->getWorldKartId(); const int my_laps = getLapForKart(my_id); const float my_progression = getDistanceDownTrackForKart(my_id); // Count karts ahead of the current kart, i.e. kart that are already finished, // have done more laps, or the same number of laps, but a greater distance. for (unsigned int j = 0 ; j < kart_amount ; j++) { if(j == kart->getWorldKartId()) continue; // don't compare a kart with itself if(m_karts[j]->isEliminated()) continue; // dismiss eliminated karts if(!kart->hasFinishedRace() && m_karts[j]->hasFinishedRace()) { p++; continue; } /* has done more or less lapses */ assert(j==m_karts[j]->getWorldKartId()); int other_laps = getLapForKart(j); if (other_laps != my_laps) { if(other_laps > my_laps) { p++; // Other kart has more lapses } continue; } // Now both karts have the same number of lapses. Test progression. // A kart is ahead if it's driven further, or driven the same // distance, but started further to the back. float other_progression = getDistanceDownTrackForKart(j); if(other_progression > my_progression || (other_progression == my_progression && m_karts[j]->getInitialPosition() > kart->getInitialPosition()) ) { p++; #if _DEBUG_PRINTS_LIKE_MAD_ std::cout << " " << p << " : " << m_karts[j]->getIdent() << " because he has is further within the track (my progression is " << my_progression << ", his progression is " << other_progression << ")\n"; #endif } } //next kart #ifndef DEBUG setKartPosition(i, p); #else rank_changed |= kart->getPosition()!=p; if (!setKartPosition(i,p)) { std::cerr << "ERROR, same rank used twice!!\n"; std::cerr << "Info used to decide ranking :\n"; for (unsigned int d=0; d<kart_amount; d++) { std::cerr << " kart " << m_karts[d]->getIdent() << " has finished(" << m_karts[d]->hasFinishedRace() << "), is at lap (" << getLapForKart(d) << "), is at distance(" << getDistanceDownTrackForKart(d) << "), is eliminated(" << m_karts[d]->isEliminated() << ")" << std::endl; } std::cerr << "Who has each ranking so far :\n"; for (unsigned int d=0; d<i; d++) { std::cerr << " " << m_karts[d]->getIdent() << " has rank " << m_karts[d]->getPosition() << std::endl; } std::cerr << " --> And " << kart->getIdent() << " is being set at rank " << p << std::endl; history->Save(); assert(false); } #endif // Switch on faster music if not already done so, if the // first kart is doing its last lap, and if the estimated // remaining time is less than 30 seconds. if(!m_faster_music_active && kart_info.m_race_lap == race_manager->getNumLaps()-1 && p==1 && useFastMusicNearEnd() && kart_info.m_estimated_finish > 0 && kart_info.m_estimated_finish - getTime() < 30.0f ) { music_manager->switchToFastMusic(); m_faster_music_active=true; } } // for i<kart_amount // Define this to get a detailled analyses each time a race position // changes. #ifdef DEBUG #undef DEBUG_KART_RANK #ifdef DEBUG_KART_RANK if(rank_changed) { std::cout << "Counting laps at "<<getTime()<<" seconds.\n"; for (unsigned int i=0; i<kart_amount; i++) { Kart* kart = m_karts[i]; if(kart->isEliminated() || kart->hasFinishedRace()) continue; KartInfo& kart_info = m_kart_info[i]; int p = 1 ; const int my_id = kart->getWorldKartId(); const int my_laps = getLapForKart(my_id); const float my_progression = getDistanceDownTrackForKart(my_id); std::cout << "counting karts ahead of " << kart->getIdent() << " (laps "<<m_kart_info[i].m_race_lap<<", progress " << my_progression<<").\n"; for (unsigned int j = 0 ; j < kart_amount ; j++) { if(j == kart->getWorldKartId()) continue; // don't compare a kart with itself if(!kart->hasFinishedRace() && m_karts[j]->hasFinishedRace()) { p++; std::cout << " " << p << " : " << m_karts[j]->getIdent() << " because he has finished.\n"; continue; } int other_laps = getLapForKart(j); if (other_laps != my_laps) { if(other_laps > my_laps) { p++; // Other kart has more lapses std::cout << " " << p << " : " << m_karts[j]->getIdent() << " because he has more laps than me.\n"; } continue; } float other_progression = getDistanceDownTrackForKart(j); if(other_progression > my_progression || (other_progression == my_progression && m_karts[j]->getInitialPosition() > kart->getInitialPosition()) ) { p++; std::cout << " " << p << " : " << m_karts[j]->getIdent() << " because he is further within the track (my progression is " << my_progression << ", his progression is " << other_progression << ")\n"; } } //next kart } // for i<kart_amount std::cout << "-------------------------------------------\n"; } // if rank_changed #endif #endif endSetKartPositions(); } // updateRacePosition
/** 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