// // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2004-2015 Steve Baker // Copyright (C) 2006-2015 Joerg Henrichs, 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 "karts/controller/local_player_controller.hpp" #include "audio/sfx_base.hpp" #include "config/player_manager.hpp" #include "config/stk_config.hpp" #include "config/user_config.hpp" #include "graphics/camera.hpp" #include "graphics/irr_driver.hpp" #include "graphics/particle_emitter.hpp" #include "graphics/particle_kind.hpp" #include "input/input_manager.hpp" #include "items/attachment.hpp" #include "items/item.hpp" #include "items/powerup.hpp" #include "karts/abstract_kart.hpp" #include "karts/controller/player_controller.hpp" #include "karts/kart_properties.hpp" #include "karts/skidding.hpp" #include "karts/rescue_animation.hpp" #include "modes/world.hpp" #include "network/network_config.hpp" #include "network/protocols/game_events_protocol.hpp" #include "network/protocols/game_protocol.hpp" #include "network/race_event_manager.hpp" #include "network/rewind_manager.hpp" #include "race/history.hpp" #include "states_screens/race_gui_base.hpp" #include "tracks/track.hpp" #include "utils/constants.hpp" #include "utils/log.hpp" #include "utils/translation.hpp" /** The constructor for a loca player kart, i.e. a player that is playing * on this machine (non-local player would be network clients). * \param kart_name Name of the kart. * \param position The starting position (1 to n). * \param player The player to which this kart belongs. * \param init_pos The start coordinates and heading of the kart. */ LocalPlayerController::LocalPlayerController(AbstractKart *kart, const int local_player_id, PerPlayerDifficulty d) : PlayerController(kart), m_sky_particles_emitter(NULL) { m_has_started = false; m_difficulty = d; m_player = StateManager::get()->getActivePlayer(local_player_id); if(m_player) m_player->setKart(kart); // Keep a pointer to the camera to remove the need to search for // the right camera once per frame later. Camera *camera = Camera::createCamera(kart, local_player_id); m_camera_index = camera->getIndex(); m_wee_sound = SFXManager::get()->createSoundSource("wee"); m_bzzt_sound = SFXManager::get()->getBuffer("bzzt"); m_ugh_sound = SFXManager::get()->getBuffer("ugh"); m_grab_sound = SFXManager::get()->getBuffer("grab_collectable"); m_full_sound = SFXManager::get()->getBuffer("energy_bar_full"); m_unfull_sound = SFXManager::get()->getBuffer("energy_bar_unfull"); m_is_above_nitro_target = false; // Attach Particle System Track *track = Track::getCurrentTrack(); #ifndef SERVER_ONLY if (UserConfigParams::m_particles_effects > 1 && track->getSkyParticles() != NULL) { track->getSkyParticles()->setBoxSizeXZ(150.0f, 150.0f); m_sky_particles_emitter = new ParticleEmitter(track->getSkyParticles(), core::vector3df(0.0f, 30.0f, 100.0f), m_kart->getNode(), true); // FIXME: in multiplayer mode, this will result in several instances // of the heightmap being calculated and kept in memory m_sky_particles_emitter->addHeightMapAffector(track); } #endif } // LocalPlayerController //----------------------------------------------------------------------------- /** Destructor for a player kart. */ LocalPlayerController::~LocalPlayerController() { m_wee_sound->deleteSFX(); if (m_sky_particles_emitter) delete m_sky_particles_emitter; } // ~LocalPlayerController //----------------------------------------------------------------------------- /** Resets the player kart for a new or restarted race. */ void LocalPlayerController::reset() { PlayerController::reset(); m_sound_schedule = false; m_has_started = false; } // reset // ---------------------------------------------------------------------------- /** Resets the state of control keys. This is used after the in-game menu to * avoid that any keys pressed at the time the menu is opened are still * considered to be pressed. */ void LocalPlayerController::resetInputState() { PlayerController::resetInputState(); m_sound_schedule = false; } // resetInputState // ---------------------------------------------------------------------------- /** This function interprets a kart action and value, and set the corresponding * entries in the kart control data structure. This function handles esp. * cases like 'press left, press right, release right' - in this case after * releasing right, the steering must switch to left again. Similarly it * handles 'press left, press right, release left' (in which case still * right must be selected). Similarly for braking and acceleration. * \param action The action to be executed. * \param value If 32768, it indicates a digital value of 'fully set' * if between 1 and 32767, it indicates an analog value, * and if it's 0 it indicates that the corresponding button * was released. * \param dry_run If set it will return if this action will trigger a * state change or not. * \return True if dry_run==true and a state change would be triggered. * If dry_run==false, it returns true. */ bool LocalPlayerController::action(PlayerAction action, int value, bool dry_run) { // Pause race doesn't need to be sent to server if (action == PA_PAUSE_RACE) { PlayerController::action(action, value); return true; } if (action == PA_ACCEL && value != 0 && !m_has_started) { m_has_started = true; if (!NetworkConfig::get()->isNetworking()) { float f = m_kart->getStartupBoostFromStartTicks( World::getWorld()->getAuxiliaryTicks()); m_kart->setStartupBoost(f); } else if (NetworkConfig::get()->isClient()) { auto ge = RaceEventManager::getInstance()->getProtocol(); assert(ge); ge->sendStartupBoost((uint8_t)m_kart->getWorldKartId()); } } // If this event does not change the control state (e.g. // it's a (auto) repeat event), do nothing. This especially // optimises traffic to the server and other clients. if (!PlayerController::action(action, value, /*dry_run*/true)) return false; // Register event with history if(!history->replayHistory()) history->addEvent(m_kart->getWorldKartId(), action, value); // If this is a client, send the action to networking layer if (NetworkConfig::get()->isNetworking() && NetworkConfig::get()->isClient() && !RewindManager::get()->isRewinding()) { if (auto gp = GameProtocol::lock()) { gp->controllerAction(m_kart->getWorldKartId(), action, value, m_steer_val_l, m_steer_val_r); } } return PlayerController::action(action, value, /*dry_run*/false); } // action //----------------------------------------------------------------------------- /** Handles steering for a player kart. */ void LocalPlayerController::steer(int ticks, int steer_val) { if(UserConfigParams::m_gamepad_debug) { RaceGUIBase* gui_base = World::getWorld()->getRaceGUI(); gui_base->clearAllMessages(); gui_base->addMessage(StringUtils::insertValues(L"steer_val %i", steer_val), m_kart, 1.0f, video::SColor(255, 255, 0, 255), false); } PlayerController::steer(ticks, steer_val); if(UserConfigParams::m_gamepad_debug) { Log::debug("LocalPlayerController", " set to: %f\n", m_controls->getSteer()); } } // steer //----------------------------------------------------------------------------- /** Updates the player kart, called once each timestep. */ void LocalPlayerController::update(int ticks) { if (UserConfigParams::m_gamepad_debug) { // Print a dividing line so that it's easier to see which events // get received in which order in the one frame. Log::debug("LocalPlayerController", "irr_driver", "-------------------------------------"); } PlayerController::update(ticks); // look backward when the player requests or // if automatic reverse camera is active #ifndef SERVER_ONLY Camera *camera = Camera::getCamera(m_camera_index); if (camera->getType() != Camera::CM_TYPE_END) { if (m_controls->getLookBack() || (UserConfigParams::m_reverse_look_threshold > 0 && m_kart->getSpeed() < -UserConfigParams::m_reverse_look_threshold)) { camera->setMode(Camera::CM_REVERSE); if (m_sky_particles_emitter) { m_sky_particles_emitter->setPosition(Vec3(0, 30, -100)); m_sky_particles_emitter->setRotation(Vec3(0, 180, 0)); } } else { if (camera->getMode() == Camera::CM_REVERSE) { camera->setMode(Camera::CM_NORMAL); if (m_sky_particles_emitter) { m_sky_particles_emitter->setPosition(Vec3(0, 30, 100)); m_sky_particles_emitter->setRotation(Vec3(0, 0, 0)); } } } } if (m_is_above_nitro_target == true && m_kart->getEnergy() < race_manager->getCoinTarget()) nitroNotFullSound(); #endif if (m_kart->getKartAnimation() && m_sound_schedule == false) { m_sound_schedule = true; } else if (!m_kart->getKartAnimation() && m_sound_schedule == true) { m_sound_schedule = false; m_kart->playSound(m_bzzt_sound); } } // update //----------------------------------------------------------------------------- /** Displays a penalty warning for player controlled karts. Called from * LocalPlayerKart::update() if necessary. */ void LocalPlayerController::displayPenaltyWarning() { PlayerController::displayPenaltyWarning(); RaceGUIBase* m=World::getWorld()->getRaceGUI(); if (m) { m->addMessage(_("Penalty time!!"), m_kart, 2.0f, GUIEngine::getSkin()->getColor("font::top"), true /* important */, false /* big font */, true /* outline */); m->addMessage(_("Don't accelerate before go"), m_kart, 2.0f, GUIEngine::getSkin()->getColor("font::normal"), true /* important */, false /* big font */, true /* outline */); } m_kart->playSound(m_bzzt_sound); } // displayPenaltyWarning //----------------------------------------------------------------------------- /** Called just before the kart position is changed. It checks if the kart was * overtaken, and if so plays a sound from the overtaking kart. */ void LocalPlayerController::setPosition(int p) { PlayerController::setPosition(p); if(m_kart->getPosition()getNumKarts(); i++ ) { AbstractKart *kart = world->getKart(i); if(kart->getPosition() == p + 1) { kart->beep(); break; } } } } // setPosition //----------------------------------------------------------------------------- /** Called when a kart finishes race. * /param time Finishing time for this kart. d*/ void LocalPlayerController::finishedRace(float time) { // This will implicitly trigger setting the first end camera to be active Camera::changeCamera(m_camera_index, Camera::CM_TYPE_END); } // finishedRace //----------------------------------------------------------------------------- /** Called when a kart hits or uses a zipper. */ void LocalPlayerController::handleZipper(bool play_sound) { PlayerController::handleZipper(play_sound); // Only play a zipper sound if it's not already playing, and // if the material has changed (to avoid machine gun effect // on conveyor belt zippers). if (play_sound || (m_wee_sound->getStatus() != SFXBase::SFX_PLAYING && m_kart->getMaterial()!=m_kart->getLastMaterial() ) ) { m_wee_sound->play(); } #ifndef SERVER_ONLY // Apply the motion blur according to the speed of the kart irr_driver->giveBoost(m_camera_index); #endif } // handleZipper //----------------------------------------------------------------------------- /** Called when a kart hits an item. It plays certain sfx (e.g. nitro full, * or item specific sounds). * \param item Item that was collected. * \param old_energy The previous energy value */ void LocalPlayerController::collectedItem(const ItemState &item_state, float old_energy) { if (old_energy < m_kart->getKartProperties()->getNitroMax() && m_kart->getEnergy() == m_kart->getKartProperties()->getNitroMax()) { m_kart->playSound(m_full_sound); } else if (race_manager->getCoinTarget() > 0 && old_energy < race_manager->getCoinTarget() && m_kart->getEnergy() >= race_manager->getCoinTarget()) { m_kart->playSound(m_full_sound); m_is_above_nitro_target = true; } else { switch(item_state.getType()) { case Item::ITEM_BANANA: m_kart->playSound(m_ugh_sound); break; case Item::ITEM_BUBBLEGUM: //More sounds are played by the kart class //See Kart::collectedItem() m_kart->playSound(m_ugh_sound); break; case Item::ITEM_TRIGGER: // no default sound for triggers break; default: m_kart->playSound(m_grab_sound); break; } } } // collectedItem //----------------------------------------------------------------------------- /** If the nitro level has gone under the nitro goal, play a bad effect sound */ void LocalPlayerController::nitroNotFullSound() { m_kart->playSound(m_unfull_sound); m_is_above_nitro_target = false; } //nitroNotFullSound // ---------------------------------------------------------------------------- /** Returns true if the player of this controller can collect achievements. * At the moment only the current player can collect them. * TODO: check this, possible all local players should be able to * collect achievements - synching to online account will happen * next time the account gets online. */ bool LocalPlayerController::canGetAchievements() const { return !RewindManager::get()->isRewinding() && m_player->getConstProfile() == PlayerManager::getCurrentPlayer(); } // canGetAchievements // ---------------------------------------------------------------------------- core::stringw LocalPlayerController::getName() const { if (NetworkConfig::get()->isNetworking()) return PlayerController::getName(); core::stringw name = m_player->getProfile()->getName(); if (m_difficulty == PLAYER_DIFFICULTY_HANDICAP) name = _("%s (handicapped)", name); return name; } // getName