/** 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
/** Handles the conversion from some input to a GameAction and its distribution * to the currently active menu. * It also handles whether the game is currently sensing input. It does so by * suppressing the distribution of the input as a GameAction. Instead the * input is stored in 'm_sensed_input' and GA_SENSE_COMPLETE is distributed. If * however the input in question has resolved to GA_LEAVE this is treated as * an attempt of the user to cancel the sensing. In that case GA_SENSE_CANCEL * is distributed. * * Note: It is the obligation of the called menu to switch of the sense mode. * */ void InputManager::dispatchInput(Input::InputType type, int deviceID, int button, Input::AxisDirection axisDirection, int value) { // Act different in input sensing mode. if (m_mode == INPUT_SENSE_KEYBOARD || m_mode == INPUT_SENSE_GAMEPAD) { inputSensing(type, deviceID, button, axisDirection, value); return; } // Abort demo mode if a key is pressed during the race in demo mode if(dynamic_cast<DemoWorld*>(World::getWorld())) { race_manager->exitRace(); StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance()); return; } StateManager::ActivePlayer* player = NULL; PlayerAction action; bool action_found = m_device_manager->translateInput(type, deviceID, button, axisDirection, value, m_mode, &player, &action); // in menus, some keyboard keys are standard (before each player selected // his device). So if a key could not be mapped to any known binding, // fall back to check the defaults. if (!action_found && StateManager::get()->getGameState() != GUIEngine::GAME && type == Input::IT_KEYBOARD && m_mode == MENU && m_device_manager->getAssignMode() == NO_ASSIGN) { action = PA_BEFORE_FIRST; if (button == KEY_UP) action = PA_MENU_UP; else if (button == KEY_DOWN) action = PA_MENU_DOWN; else if (button == KEY_LEFT) action = PA_MENU_LEFT; else if (button == KEY_RIGHT) action = PA_MENU_RIGHT; else if (button == KEY_SPACE) action = PA_MENU_SELECT; else if (button == KEY_RETURN) action = PA_MENU_SELECT; if (button == KEY_RETURN && GUIEngine::ModalDialog::isADialogActive()) { GUIEngine::ModalDialog::onEnterPressed(); } if (action != PA_BEFORE_FIRST) { action_found = true; player = NULL; } } // do something with the key if it matches a binding if (action_found) { // If we're in the kart menu awaiting new players, do special things // when a device presses fire or rescue if (m_device_manager->getAssignMode() == DETECT_NEW) { // Player is unjoining if ((player != NULL) && (action == PA_RESCUE || action == PA_MENU_CANCEL ) ) { // returns true if the event was handled if (KartSelectionScreen::getInstance()->playerQuit( player )) { return; // we're done here } } /* The way this is currently structured, any time an event is received from an input device that is not associated with a player and the device manager is in DETECT_NEW mode, the event is ignored, unless it is a PA_FIRE event (a player is joining) perhaps it will be good to let unassigned devices back out of the kart selection menu? */ else if (player == NULL) { // New player is joining if (action == PA_FIRE || action == PA_MENU_SELECT) { InputDevice *device = NULL; if (type == Input::IT_KEYBOARD) { //std::cout << "==== New Player Joining with Key " << // button << " ====" << std::endl; device = m_device_manager->getKeyboardFromBtnID(button); } else if (type == Input::IT_STICKBUTTON || type == Input::IT_STICKMOTION ) { device = m_device_manager->getGamePadFromIrrID(deviceID); } if (device != NULL) { KartSelectionScreen::getInstance()->playerJoin(device, false ); } } return; // we're done here, ignore devices that aren't // associated with players } } // ... when in-game if (StateManager::get()->getGameState() == GUIEngine::GAME && !GUIEngine::ModalDialog::isADialogActive() ) { if (player == NULL) { // Prevent null pointer crash return; } // Find the corresponding PlayerKart from our ActivePlayer instance AbstractKart* pk = player->getKart(); if (pk == NULL) { std::cerr << "Error, trying to process action for an unknown player\n"; return; } Controller* controller = pk->getController(); if (controller != NULL) controller->action(action, abs(value)); } // ... when in menus else { // reset timer when released if (abs(value) == 0 && type == Input::IT_STICKBUTTON) { m_timer_in_use = false; m_timer = 0; } // When in master-only mode, we can safely assume that players // are set up, contrarly to early menus where we accept every // input because players are not set-up yet if (m_master_player_only && player == NULL) { if (type == Input::IT_STICKMOTION || type == Input::IT_STICKBUTTON) { GamePadDevice* gp = getDeviceList()->getGamePadFromIrrID(deviceID); if (gp != NULL && abs(value)>gp->m_deadzone) { //I18N: message shown when an input device is used but // is not associated to any player GUIEngine::showMessage( _("Ignoring '%s', you needed to join earlier to play!", irr::core::stringw(gp->m_name.c_str()).c_str()) ); } } return; } // menu input if (!m_timer_in_use) { if (abs(value) > Input::MAX_VALUE*2/3) { m_timer_in_use = true; m_timer = 0.25; } // player may be NULL in early menus, before player setup has // been performed int playerID = (player == NULL ? 0 : player->getID()); // If only the master player can act, and this player is not // the master, ignore his input if (m_device_manager->getAssignMode() == ASSIGN && m_master_player_only && playerID != PLAYER_ID_GAME_MASTER) { //I18N: message shown when a player that isn't game master //I18N: tries to modify options that only the game master //I18N: is allowed to GUIEngine::showMessage( _("Only the Game Master may act at this point!")); return; } // all is good, pass the translated input event on to the // event handler GUIEngine::EventHandler::get() ->processGUIAction(action, deviceID, abs(value), type, playerID); } } } else if (type == Input::IT_KEYBOARD) { // keyboard press not handled by device manager / bindings. // Check static bindings... handleStaticAction( button, value ); } } // input
/** 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
/** Updates skidding status. * \param dt Time step size. * \param is_on_ground True if the kart is on ground. * \param steering Raw steering of the kart [-1,1], i.e. not adjusted by * the kart's max steering angle. * \param skidding True if the skid button is pressed. */ void Skidding::update(float dt, bool is_on_ground, float steering, KartControl::SkidControl skidding) { // If a kart animation is shown, stop all skidding bonuses. if(m_kart->getKartAnimation()) { reset(); return; } #ifdef SKIDDING_PARTICLE_DEBUG // This code will cause skidmarks to be displayed all the time, even // when the kart is not moving. m_kart->getKartGFX()->setCreationRateRelative(KartGFX::KGFX_SKIDL, 0.5f); m_kart->getKartGFX()->setCreationRateRelative(KartGFX::KGFX_SKIDR, 0.5f); #endif // No skidding backwards or while stopped if(m_kart->getSpeed() < m_min_skid_speed && m_skid_state != SKID_NONE && m_skid_state != SKID_BREAK) { m_skid_state = SKID_BREAK; m_kart->getKartGFX()->setCreationRateAbsolute(KartGFX::KGFX_SKIDL, 0); m_kart->getKartGFX()->setCreationRateAbsolute(KartGFX::KGFX_SKIDR, 0); } m_skid_bonus_ready = false; if (is_on_ground) { if((fabs(steering) > 0.001f) && m_kart->getSpeed()>m_min_skid_speed && (skidding==KartControl::SC_LEFT||skidding==KartControl::SC_RIGHT)) { m_skid_factor += m_skid_increase *dt/m_time_till_max_skid; } else if(m_skid_factor>1.0f) { m_skid_factor *= m_skid_decrease; } } else { m_skid_factor = 1.0f; // Lose any skid factor as soon as we fly } if(m_skid_factor>m_skid_max) m_skid_factor = m_skid_max; else if(m_skid_factor<1.0f) m_skid_factor = 1.0f; // If skidding was started and a graphical jump should still // be displayed, update the data if(m_remaining_jump_time>0) { m_jump_speed -= World::getWorld()->getTrack()->getGravity()*dt; m_gfx_jump_offset += m_jump_speed * dt; m_remaining_jump_time -= dt; if(m_remaining_jump_time<0) { m_remaining_jump_time = 0.0f; m_gfx_jump_offset = 0.0f; } } // This is only reached if the new skidding is enabled // --------------------------------------------------- // There are four distinct states related to skidding, controlled // by m_skid_state: // SKID_NONE: no skidding is happening. From here SKID_ACCUMULATE // is reached when the skid key is pressed. // SKID_ACCUMULATE_{LEFT,RIGHT}: // The kart is still skidding. The skidding time will be // accumulated in m_skid_time, and once the minimum time for a // bonus is reached, the "bonus gfx now available" gfx is shown. // If the skid button is not pressed anymore, this will trigger // a potential bonus. Also the rotation of the physical body to // be in synch with the graphical kart is started (which is // independently handled in the kart physics). // SKID_SHOW_GFX_{LEFT<RIGHT} // Shows the skidding gfx while the bonus is available. // FIXME: what should we do if skid key is pressed while still in // SKID_SHOW_GFX??? Adjusting the body rotation is difficult. // For now skidding will only start again once SKID_SHOW_GFX // is changed to SKID_NONE. switch(m_skid_state) { case SKID_NONE: { if(skidding!=KartControl::SC_LEFT && skidding!=KartControl::SC_RIGHT) break; // Don't allow skidding while the kart is (apparently) // still in the air, or when the kart is too slow if(m_remaining_jump_time>0 || m_kart->getSpeed() <m_min_skid_speed) break; m_skid_state = skidding==KartControl::SC_RIGHT ? SKID_ACCUMULATE_RIGHT : SKID_ACCUMULATE_LEFT; // Add a little jump to the kart. Determine the vertical speed // necessary for the kart to go 0.5*jump_time up (then it needs // the same time to come down again), based on v = gravity * t. // Then use this speed to determine the impulse necessary to // reach this speed. float v = World::getWorld()->getTrack()->getGravity() * 0.5f*m_physical_jump_time; btVector3 imp(0, v / m_kart->getBody()->getInvMass(),0); m_kart->getVehicle()->getRigidBody()->applyCentralImpulse(imp); // Some karts might use a graphical-only jump. Set it up: m_jump_speed = World::getWorld()->getTrack()->getGravity() * 0.5f*m_graphical_jump_time; m_remaining_jump_time = m_graphical_jump_time; #ifdef SKID_DEBUG #define SPEED 20.0f updateSteering(steering, dt); m_actual_curve->clear(); m_actual_curve->setVisible(true); m_predicted_curve->clear(); m_predicted_curve->setVisible(true); m_predicted_curve->setPosition(m_kart->getXYZ()); m_predicted_curve->setHeading(m_kart->getHeading()); float angle = m_kart->getKartProperties() ->getMaxSteerAngle(m_kart->getSpeed()) * fabsf(getSteeringFraction()); angle = m_kart->getKartProperties() ->getMaxSteerAngle(SPEED) * fabsf(getSteeringFraction()); float r = m_kart->getKartProperties()->getWheelBase() / asin(angle)*1.0f; const int num_steps = 50; float dx = 2*r / num_steps; for(float x = 0; x <=2*r; x+=dx) { float real_x = m_skid_state==SKID_ACCUMULATE_LEFT ? -x : x; Vec3 xyz(real_x, 0.2f, sqrt(r*r-(r-x)*(r-x))*(1.0f+SPEED/150.0f) *(1+(angle/m_kart->getKartProperties()->getMaxSteerAngle(SPEED)-0.6f)*0.1f)); Vec3 xyz1=m_kart->getTrans()(xyz); Log::debug("Skidding", "predict %f %f %f speed %f angle %f", xyz1.getX(), xyz1.getY(), xyz1.getZ(), m_kart->getSpeed(), angle); m_predicted_curve->addPoint(xyz); } #endif m_skid_time = 0; // fallthrough } case SKID_BREAK: { break; } case SKID_ACCUMULATE_LEFT: case SKID_ACCUMULATE_RIGHT: { #ifdef SKID_DEBUG Vec3 v=m_kart->getVelocity(); if(v.length()>5) { float r = SPEED/sqrt(v.getX()*v.getX() + v.getZ()*v.getZ()); v.setX(v.getX()*r); v.setZ(v.getZ()*r); m_kart->getBody()->setLinearVelocity(v); } m_actual_curve->addPoint(m_kart->getXYZ()); Log::debug("Skidding", "actual %f %f %f turn %f speed %f angle %f", m_kart->getXYZ().getX(),m_kart->getXYZ().getY(),m_kart->getXYZ().getZ(), m_real_steering, m_kart->getSpeed(), m_kart->getKartProperties()->getMaxSteerAngle(m_kart->getSpeed())); #endif m_skid_time += dt; float bonus_time, bonus_speed, bonus_force; unsigned int level = getSkidBonus(&bonus_time, &bonus_speed, &bonus_force); // If at least level 1 bonus is reached, show appropriate gfx if(level>0) { m_skid_bonus_ready = true; m_kart->getKartGFX()->setSkidLevel(level); m_kart->getKartGFX()->updateSkidLight(level); } // If player stops skidding, trigger bonus, and change state to // SKID_SHOW_GFX_* if(skidding == KartControl::SC_NONE) { m_skid_state = m_skid_state == SKID_ACCUMULATE_LEFT ? SKID_SHOW_GFX_LEFT : SKID_SHOW_GFX_RIGHT; float t = std::min(m_skid_time, m_skid_visual_time); t = std::min(t, m_skid_revert_visual_time); float vso = getVisualSkidRotation(); btVector3 rot(0, vso*m_post_skid_rotate_factor, 0); m_kart->getVehicle()->setTimedRotation(t, rot); // skid_time is used to count backwards for the GFX m_skid_time = t; if(bonus_time>0) { m_kart->getKartGFX() ->setCreationRateRelative(KartGFX::KGFX_SKIDL, 1.0f); m_kart->getKartGFX() ->setCreationRateRelative(KartGFX::KGFX_SKIDR, 1.0f); m_kart->m_max_speed-> instantSpeedIncrease(MaxSpeed::MS_INCREASE_SKIDDING, bonus_speed, bonus_speed, bonus_force, bonus_time, /*fade-out-time*/ 1.0f); StateManager::ActivePlayer *c = m_kart->getController()->getPlayer(); if (c && c->getConstProfile() == PlayerManager::getCurrentPlayer()) { PlayerManager::increaseAchievement(AchievementInfo::ACHIEVE_SKIDDING, "skidding"); } } else { m_kart->getKartGFX() ->setCreationRateAbsolute(KartGFX::KGFX_SKIDL, 0); m_kart->getKartGFX() ->setCreationRateAbsolute(KartGFX::KGFX_SKIDR, 0); } } break; } // case case SKID_SHOW_GFX_LEFT: case SKID_SHOW_GFX_RIGHT: m_skid_time -= dt; if(m_skid_time<=0) { m_skid_time = 0; m_kart->getKartGFX() ->setCreationRateAbsolute(KartGFX::KGFX_SKIDL, 0); m_kart->getKartGFX() ->setCreationRateAbsolute(KartGFX::KGFX_SKIDR, 0); m_kart->getKartGFX()->updateSkidLight(0); m_skid_state = SKID_NONE; } } // switch updateSteering(steering, dt); } // update