/** 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
/** Updates all status information, called once per frame. * \param dt Duration of time step. */ void WorldStatus::update(const float dt) { switch(m_phase) { // Note: setup phase must be a separate phase, since the race_manager // checks the phase when updating the camera: in the very first time // step dt is large (it includes loading time), so the camera might // tilt way too much. A separate setup phase for the first frame // simplifies this handling case SETUP_PHASE: m_auxiliary_timer = 0.0f; m_phase = TRACK_INTRO_PHASE; if (UserConfigParams::m_music && m_play_racestart_sounds) { m_track_intro_sound->play(); } return; case TRACK_INTRO_PHASE: m_auxiliary_timer += dt; // Work around a bug that occurred on linux once: // the sfx_manager kept on reporting that it is playing, // while it was not - so STK would never reach the ready // ... phase. Since the sound effect is about 3 seconds // long, we use the aux timer to force the next phase // after 3.5 seconds. if(m_track_intro_sound->getStatus()==SFXManager::SFX_PLAYING && m_auxiliary_timer<3.5f) return; m_auxiliary_timer = 0.0f; if (m_play_racestart_sounds) m_prestart_sound->play(); m_phase = READY_PHASE; for(unsigned int i=0; i<World::getWorld()->getNumKarts(); i++) World::getWorld()->getKart(i)->startEngineSFX(); break; case READY_PHASE: if(m_auxiliary_timer>1.0) { if (m_play_racestart_sounds) m_prestart_sound->play(); m_phase=SET_PHASE; } m_auxiliary_timer += dt; // In artist debug mode, when without opponents, skip the ready/set/go counter faster if (UserConfigParams::m_artist_debug_mode && race_manager->getNumberOfKarts() == 1) m_auxiliary_timer += dt*6; return; case SET_PHASE : if(m_auxiliary_timer>2.0) { // set phase is over, go to the next one m_phase=GO_PHASE; if (m_play_racestart_sounds) m_start_sound->play(); World::getWorld()->getTrack()->startMusic(); // event onGo(); } m_auxiliary_timer += dt; // In artist debug mode, when without opponents, skip the ready/set/go counter faster if (UserConfigParams::m_artist_debug_mode && race_manager->getNumberOfKarts() == 1) m_auxiliary_timer += dt*6; return; case GO_PHASE : if (m_auxiliary_timer>2.5f && music_manager->getCurrentMusic()) music_manager->startMusic(music_manager->getCurrentMusic()); if(m_auxiliary_timer>3.0f) // how long to display the 'go' message { m_phase=MUSIC_PHASE; } m_auxiliary_timer += dt; // In artist debug mode, when without opponents, skip the ready/set/go counter faster if (UserConfigParams::m_artist_debug_mode && race_manager->getNumberOfKarts() == 1) m_auxiliary_timer += dt*6; break; case MUSIC_PHASE: // how long to display the 'music' message if(m_auxiliary_timer>stk_config->m_music_credit_time) m_phase=RACE_PHASE; m_auxiliary_timer += dt; break; case RACE_PHASE: // Nothing to do for race phase, switch to delay finish phase // happens when break; case DELAY_FINISH_PHASE : { m_auxiliary_timer += dt; // Change to next phase if delay is over if(m_auxiliary_timer > stk_config->m_delay_finish_time) { m_phase = RESULT_DISPLAY_PHASE; terminateRace(); } break; } case RESULT_DISPLAY_PHASE : { // Wait for the race over GUI/modal dialog to appear // Previously the in race race over results are shown, // and getCurrent() returns NULL. RaceOverDialog *m = dynamic_cast<RaceOverDialog*>(GUIEngine::ModalDialog ::getCurrent()); if( m && m->menuIsFinished() ) { m_phase = FINISH_PHASE; } break; } case FINISH_PHASE: // Nothing to do here. break; default: break; } switch(m_clock_mode) { case CLOCK_CHRONO: m_time += dt; break; case CLOCK_COUNTDOWN: // stop countdown when race is over if (m_phase == RESULT_DISPLAY_PHASE || m_phase == FINISH_PHASE) { m_time = 0.0f; break; } m_time -= dt; if(m_time <= 0.0) { // event countdownReachedZero(); } break; default: break; } } // update
/** Updates the world time and clock (which might be running backwards), and * all status information, called once per frame at the end of the main * loop. * \param ticks Number of ticks (physics time steps) - should be 1. */ void WorldStatus::updateTime(int ticks) { switch (m_phase.load()) { // Note: setup phase must be a separate phase, since the race_manager // checks the phase when updating the camera: in the very first time // step dt is large (it includes loading time), so the camera might // tilt way too much. A separate setup phase for the first frame // simplifies this handling case SETUP_PHASE: m_auxiliary_ticks= 0; m_phase = TRACK_INTRO_PHASE; if (m_play_track_intro_sound) { m_track_intro_sound->play(); } if (Weather::getInstance()) { Weather::getInstance()->playSound(); } return; // Do not increase time case TRACK_INTRO_PHASE: m_auxiliary_ticks++; if (UserConfigParams::m_artist_debug_mode && !NetworkConfig::get()->isNetworking() && race_manager->getNumberOfKarts() - race_manager->getNumSpareTireKarts() == 1 && race_manager->getTrackName() != "tutorial") { m_auxiliary_ticks += 6; } if (!m_play_track_intro_sound) { startEngines(); } // Wait before ready phase if (m_auxiliary_ticks < stk_config->time2Ticks(3.0f)) return; m_auxiliary_ticks = 0; // In a networked game the client needs to wait for a notification // from the server that all clients and the server are ready to // start the game. The server will actually wait for all clients // to confirm that they have started the race before starting // itself. In a normal race, this phase is skipped and the race // starts immediately. if (NetworkConfig::get()->isNetworking()) { m_phase = WAIT_FOR_SERVER_PHASE; // In networked races, inform the start game protocol that // the world has been setup if (!m_live_join_world) { auto lobby = LobbyProtocol::get<LobbyProtocol>(); assert(lobby); lobby->finishedLoadingWorld(); } } else { if (m_play_ready_set_go_sounds) m_prestart_sound->play(); m_phase = READY_PHASE; } return; // Don't increase time case WAIT_FOR_SERVER_PHASE: { if (m_live_join_world) { m_auxiliary_ticks++; // Add 3 seconds delay before telling server finish loading // world, so previous (if any) disconnected player has left // fully if (m_auxiliary_ticks == stk_config->time2Ticks(3.0f)) { auto cl = LobbyProtocol::get<ClientLobby>(); assert(cl); cl->finishedLoadingWorld(); #ifndef ANDROID static bool helper_msg_shown = false; if (!helper_msg_shown && cl->isSpectator()) { helper_msg_shown = true; cl->addSpectateHelperMessage(); } #endif } return; } return; // Don't increase time } case SERVER_READY_PHASE: { auto lobby = LobbyProtocol::get<LobbyProtocol>(); if (lobby && lobby->isRacing()) { if (m_play_ready_set_go_sounds) m_prestart_sound->play(); m_phase = READY_PHASE; } return; // Don't increase time } case READY_PHASE: startEngines(); // One second if (m_auxiliary_ticks > stk_config->getPhysicsFPS()) { if (m_play_ready_set_go_sounds) { m_prestart_sound->play(); } m_phase = SET_PHASE; } m_auxiliary_ticks++; // In artist debug mode, when without opponents, skip the // ready/set/go counter faster if (UserConfigParams::m_artist_debug_mode && !NetworkConfig::get()->isNetworking() && race_manager->getNumberOfKarts() - race_manager->getNumSpareTireKarts() == 1 && race_manager->getTrackName() != "tutorial") { m_auxiliary_ticks += 6; } return; // Do not increase time case SET_PHASE: if (m_auxiliary_ticks > 2*stk_config->getPhysicsFPS()) { // set phase is over, go to the next one m_phase = GO_PHASE; if (m_play_ready_set_go_sounds) { m_start_sound->play(); } // event onGo(); // In artist debug mode, when without opponents, // skip the ready/set/go counter faster m_start_music_ticks = UserConfigParams::m_artist_debug_mode && !NetworkConfig::get()->isNetworking() && race_manager->getNumberOfKarts() - race_manager->getNumSpareTireKarts() == 1 && race_manager->getTrackName() != "tutorial" ? stk_config->time2Ticks(0.2f) : stk_config->time2Ticks(1.0f); // how long to display the 'music' message // no graphics mode goes race phase at 3 seconds; m_race_ticks = ProfileWorld::isNoGraphics() ? stk_config->time2Ticks(3.0f) : stk_config->time2Ticks(stk_config->m_music_credit_time); } m_auxiliary_ticks++; // In artist debug mode, when without opponents, // skip the ready/set/go counter faster if (UserConfigParams::m_artist_debug_mode && !NetworkConfig::get()->isNetworking() && race_manager->getNumberOfKarts() - race_manager->getNumSpareTireKarts() == 1 && race_manager->getTrackName() != "tutorial") { m_auxiliary_ticks += 6; } return; // Do not increase time case GO_PHASE: { if (m_start_music_ticks != -1 && m_count_up_ticks >= m_start_music_ticks) { m_start_music_ticks = -1; if (music_manager->getCurrentMusic() && !music_manager->getCurrentMusic()->isPlaying()) { music_manager->startMusic(); } m_phase = MUSIC_PHASE; } break; // Now the world time starts } case MUSIC_PHASE: { // Start the music here when starting fast if (UserConfigParams::m_race_now) { music_manager->startMusic(); UserConfigParams::m_race_now = false; } if (m_race_ticks != -1 && m_count_up_ticks >= m_race_ticks) { m_race_ticks = -1; m_phase = RACE_PHASE; } break; } case RACE_PHASE: // Nothing to do for race phase, switch to delay finish phase // happens when break; case DELAY_FINISH_PHASE: { m_auxiliary_ticks++; // Change to next phase if delay is over if (m_auxiliary_ticks > stk_config->time2Ticks(stk_config->m_delay_finish_time)) { m_phase = RESULT_DISPLAY_PHASE; terminateRace(); } break; } case RESULT_DISPLAY_PHASE: { break; } case FINISH_PHASE: case IN_GAME_MENU_PHASE: // Nothing to do here. break; case GOAL_PHASE: // Nothing to do here as well. break; default: break; } IrrlichtDevice *device = irr_driver->getDevice(); switch (m_clock_mode) { case CLOCK_CHRONO: if (!device->getTimer()->isStopped()) { m_time_ticks++; m_time = stk_config->ticks2Time(m_time_ticks); m_count_up_ticks++; } break; case CLOCK_COUNTDOWN: // stop countdown when race is over if (m_phase == RESULT_DISPLAY_PHASE || m_phase == FINISH_PHASE) { m_time_ticks = 0; m_time = 0.0f; m_count_up_ticks = 0; break; } if (!device->getTimer()->isStopped()) { m_time_ticks--; m_time = stk_config->ticks2Time(m_time_ticks); m_count_up_ticks++; } if (m_time_ticks <= 0) { // event countdownReachedZero(); } break; default: break; } // switch m_phase } // update