//  $Id$
//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2006 SuperTuxKart-Team
//
//  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 "modes/world.hpp"

#include <assert.h>
#include <sstream>
#include <stdexcept>
#include <algorithm>
#include <ctime>

#include "audio/music_manager.hpp"
#include "audio/sfx_base.hpp"
#include "audio/sfx_manager.hpp"
#include "challenges/unlock_manager.hpp"
#include "config/user_config.hpp"
#include "graphics/camera.hpp"
#include "io/file_manager.hpp"
#include "items/projectile_manager.hpp"
#include "karts/controller/default_ai_controller.hpp"
#include "karts/controller/new_ai_controller.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/controller/end_controller.hpp"
#include "karts/kart_properties_manager.hpp"
#include "network/network_manager.hpp"
#include "network/race_state.hpp"
#include "physics/btKart.hpp"
#include "physics/triangle_mesh.hpp"
#include "race/highscore_manager.hpp"
#include "race/history.hpp"
#include "race/race_manager.hpp"
#include "states_screens/state_manager.hpp"
#include "states_screens/race_gui_base.hpp"
#include "states_screens/race_gui.hpp"
#include "states_screens/minimal_race_gui.hpp"
#include "states_screens/race_result_gui.hpp"
#include "tracks/track.hpp"
#include "tracks/track_manager.hpp"
#include "utils/constants.hpp"
#include "utils/translation.hpp"
#include "utils/string_utils.hpp"

World* World::m_world = NULL;

/** The main world class is used to handle the track and the karts.
 *  The end of the race is detected in two phases: first the (abstract)
 *  function isRaceOver, which must be implemented by all game modes,
 *  must return true. In which case enterRaceOverState is called. At 
 *  this time a winning (or losing) animation can be played. The WorldStatus
 *  class will in its enterRaceOverState switch to DELAY_FINISH_PHASE,
 *  but the remaining AI kart will keep on racing during that time.
 *  After a time period specified in stk_config.xml WorldStatus will
 *  switch to FINISH_PHASE and call terminateRace. Now the finishing status
 *  of all karts is set (i.e. in a normal race the arrival time for karts
 *  will be estimated), highscore is updated, and the race result gui
 *  is being displayed.
 */
//-----------------------------------------------------------------------------
/** Constructor. Note that in the constructor it is not possible to call any
 *  functions that use World::getWorld(), since this is only defined
 *  after the constructor. Those functions must be called in the init() 
 *  function, which is called immediately after the constructor.
 */
World::World() : WorldStatus(), m_clear_color(255,100,101,140)
{
    m_physics           = NULL;
    m_race_gui          = NULL;
    m_saved_race_gui    = NULL;
    m_use_highscores    = true;
    m_track             = NULL;
    m_clear_back_buffer = false;
    m_schedule_pause    = false;
    m_schedule_unpause  = false;
    
    WorldStatus::setClockMode(CLOCK_CHRONO);
}   // World

// ----------------------------------------------------------------------------
/** This function is called after the World constructor. In init() functions
 *  can be called that use World::getWorld(). The init function is 
 *  called immediately after the constructor.
 */
void World::init()
{
    race_state            = new RaceState();
    m_faster_music_active = false;
    m_fastest_lap         = 9999999.9f;
    m_fastest_kart        = 0;
    m_eliminated_karts    = 0;
    m_eliminated_players  = 0;
    m_num_players         = 0;

    // Create the race gui before anything else is attached to the scene node
    // (which happens when the track is loaded). This allows the race gui to
    // do any rendering on texture.
	if(UserConfigParams::m_minimal_race_gui)
		m_race_gui = new MinimalRaceGUI();
	else
		m_race_gui = new RaceGUI();

    // Grab the track file
    m_track = track_manager->getTrack(race_manager->getTrackName());
    if(!m_track)
    {
        std::ostringstream msg;
        msg << "Track '" << race_manager->getTrackName() 
            << "' not found.\n";
        throw std::runtime_error(msg.str());
    }

    // Create the physics
    m_physics = new Physics();

    unsigned int num_karts = race_manager->getNumberOfKarts();

    assert(num_karts > 0);

    // Load the track models - this must be done before the karts so that the
    // karts can be positioned properly on (and not in) the tracks.
    m_track->loadTrackModel(this);

    for(unsigned int i=0; i<num_karts; i++)
    {
        const std::string& kart_ident = 
             history->replayHistory() ? history->getKartIdent(i)
                                      : race_manager->getKartIdent(i);
        int local_player_id           = race_manager->getKartLocalPlayerId(i);
        int global_player_id          = race_manager->getKartGlobalPlayerId(i);
        Kart* newkart = createKart(kart_ident, i, local_player_id,  
                                   global_player_id);
        m_karts.push_back(newkart);
        newkart->setWorldKartId(m_karts.size()-1);
        m_track->adjustForFog(newkart->getNode());
    }  // for i

    resetAllKarts();
    // Note: track reset must be called after all karts exist, since check
    // objects need to allocate data structures depending on the number
    // of karts.
    m_track->reset();

    if(!history->replayHistory()) history->initRecording();
    network_manager->worldLoaded();
    
    powerup_manager->updateWeightsForRace(num_karts);
    // erase messages left over
    RaceGUIBase* m = World::getWorld()->getRaceGUI();
    if (m) m->clearAllMessages();
}   // init

//-----------------------------------------------------------------------------
/** Creates a kart, having a certain position, starting location, and local
 *  and global player id (if applicable).
 *  \param kart_ident Identifier of the kart to create.
 *  \param index Index of the kart.
 *  \param local_player_id If the kart is a player kart this is the index of
 *         this player on the local machine.
 *  \param global_player_id If the kart is a player kart this is the index of
 *         this player globally (i.e. including network players).
 */
Kart *World::createKart(const std::string &kart_ident, int index,
                        int local_player_id, int global_player_id)
{
    int position           = index+1;
    btTransform init_pos   = m_track->getStartTransform(index);
    Kart *new_kart         = new Kart(kart_ident, m_track, position, (local_player_id == 0), init_pos,
                                      race_manager->getKartType(index));
    Controller *controller = NULL;
    switch(race_manager->getKartType(index))
    {
    case RaceManager::KT_PLAYER:
        controller = new PlayerController(new_kart, 
                                          StateManager::get()->getActivePlayer(local_player_id),
                                          local_player_id);
        m_num_players ++;
        break;
    case RaceManager::KT_NETWORK_PLAYER:
		break;  // Avoid compiler warning about enum not handled.
        //controller = new NetworkController(kart_ident, position, init_pos,
        //                          global_player_id);
        //m_num_players++;
        //break;
    case RaceManager::KT_AI:
        controller = loadAIController(new_kart);
        break;
    case RaceManager::KT_GHOST:
        break;
    case RaceManager::KT_LEADER:
        break;
    }
    
    new_kart->setController(controller);
    
    return new_kart;
}   // createKart

//-----------------------------------------------------------------------------
/** Creates an AI controller for the kart.
 *  \param kart The kart to be controlled by an AI.
 */
Controller* World::loadAIController(Kart *kart)
{
    Controller *controller;
    // const int NUM_ROBOTS = 1;
    // For now: instead of random switching, use each
    // robot in turns: switch(m_random.get(NUM_ROBOTS))
    // static int turn=1;
    // turn=1-turn;

    // For now disable the new AI.
    int turn=0;
    switch(turn)
    {
        case 0:
            controller = new DefaultAIController(kart);
            break;
        case 1:
            controller = new NewAIController(kart);
            break;
        default:
            fprintf(stderr, "Warning: Unknown robot, using default.\n");
            controller = new DefaultAIController(kart);
            break;
    }

    return controller;
}   // loadAIController

//-----------------------------------------------------------------------------
World::~World()
{
    // In case that a race is aborted (e.g. track not found) m_track is 0.
    if(m_track)
        m_track->cleanup();

    // Delete the in-race-gui:
    if(m_saved_race_gui)
    {
        // If there is a save race gui, this means that the result gui is
        // currently being shown. The race result gui is a screen and so
        // is deleted by the state manager. So we only have to delete
        // the actual race gui:
        delete m_saved_race_gui;
    }
    else
    {
        // No race result gui is shown, so m_race_gui is the in-race
        // gui and this must be deleted.
        delete m_race_gui;
    }
    delete race_state;

    for ( unsigned int i = 0 ; i < m_karts.size() ; i++ )
        delete m_karts[i];

    m_karts.clear();
    projectile_manager->cleanup();
    // In case that the track is not found, m_physics is still undefined.
    if(m_physics)
        delete m_physics;

    music_manager->stopMusic();
    m_world = NULL;
}   // ~World

//-----------------------------------------------------------------------------
/** Called when 'go' is being displayed for the first time. Here the brakes
 *  of the karts are released.
 */
void World::onGo()
{
    // Reset the brakes now that the prestart 
    // phase is over (braking prevents the karts 
    // from sliding downhill)
    for(unsigned int i=0; i<m_karts.size(); i++) 
    {
        m_karts[i]->resetBrakes();
    }
}   // onGo

//-----------------------------------------------------------------------------
/** 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->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

//-----------------------------------------------------------------------------
/** Waits till each kart is resting on the ground
 *
 * Does simulation steps still all karts reach the ground, i.e. are not
 * moving anymore
 */
void World::resetAllKarts()
{
    // If track checking is requested, check all rescue positions if
    // they are heigh enough.
    if(race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
        UserConfigParams::m_track_debug)
    {
        const QuadGraph &qg=m_track->getQuadGraph();
        Vec3 eps = Vec3(0,1.5f*m_karts[0]->getKartHeight(),0);
        for(unsigned int quad=0; quad<qg.getNumNodes(); quad++)
        {
            const Quad &q   = qg.getQuad(quad);
            const Vec3 center = q.getCenter();
            // We have to test for all karts, since the karts have different
            // heights and so things might change from kart to kart.
            for(unsigned int kart_id=0; kart_id<m_karts.size(); kart_id++)
            {
                Kart *kart = m_karts[kart_id];
                kart->setXYZ(center);
    
                btQuaternion heading(btVector3(0.0f, 1.0f, 0.0f),
                                     m_track->getAngle(quad) );
                kart->setRotation(heading);

                btTransform pos;
                pos.setOrigin(center+eps);
                pos.setRotation(btQuaternion(btVector3(0.0f, 1.0f, 0.0f),
                                m_track->getAngle(quad))                 );
                kart->getBody()->setCenterOfMassTransform(pos);
                bool kart_over_ground = m_physics->projectKartDownwards(kart);
                if(kart_over_ground)
                {
                    const Vec3 &xyz = kart->getTrans().getOrigin()+Vec3(0,0.3f,0);
                    ((TerrainInfo*)kart)->update(xyz);
                    const Material *material = kart->getMaterial();
                    if(!material || material->isDriveReset())
                        kart_over_ground = false;
                }
                if(!kart_over_ground)
                {
                    printf("Kart '%s' not over quad '%d'\n",
                        kart->getIdent().c_str(), quad);
                    printf("Center point: %f %f %f\n",
                        center.getX(), center.getY(), center.getZ());

                }
            }   // for kart_id<m_karts.size()
        }   // for quad < quad_graph.getNumNodes

        for(unsigned int kart_id=0; kart_id<m_karts.size(); kart_id++)
        {
            // Reset the karts back to the original start position.
            // This call is a bit of an overkill, but setting the correct
            // transforms, positions, motion state is a bit of a hassle.
            m_karts[kart_id]->reset();
        }

    }   // if m_track_debug


    m_schedule_pause = false;
    m_schedule_unpause = false;
    
    //Project karts onto track from above. This will lower each kart so
    //that at least one of its wheel will be on the surface of the track
    for ( KartList::iterator i=m_karts.begin(); i!=m_karts.end(); i++)
    {
        ///start projection from top of kart
        btVector3 up_offset(0, 0.5f * ((*i)->getKartHeight()), 0);
        (*i)->getVehicle()->getRigidBody()->translate (up_offset);

        bool kart_over_ground = m_physics->projectKartDownwards(*i);

        if (!kart_over_ground)
        {
            fprintf(stderr, "ERROR: no valid starting position for kart %d on track %s.\n",
                    (int)(i-m_karts.begin()), m_track->getIdent().c_str());
            if (UserConfigParams::m_artist_debug_mode)
            {
                fprintf(stderr, "Activating fly mode.\n");
                (*i)->flyUp();
                continue;
            }
            else
            {
                exit(-1);
            }
        }
    }

    bool all_finished=false;
    // kart->isInRest() is not fully correct, since it only takes the
    // velocity in count, which might be close to zero when the kart
    // is just hitting the floor, before being pushed up again by
    // the suspension. So we just do a longer initial simulation,
    // which should be long enough for all karts to be firmly on ground.
    for(int i=0; i<60; i++) m_physics->update(1.f/60.f);

    // Stil wait will all karts are in rest (and handle the case that a kart
    // fell through the ground, which can happen if a kart falls for a long
    // time, therefore having a high speed when hitting the ground.
    while(!all_finished)
    {
        m_physics->update(1.f/60.f);
        all_finished=true;
        for ( KartList::iterator i=m_karts.begin(); i!=m_karts.end(); i++)
        {
            if(!(*i)->isInRest())
            {
                Vec3            normal;
                Vec3            hit_point;
                const Material *material;
                // We can't use (*i)->getXYZ(), since this is only defined
                // after update() was called. Instead we have to get the
                // real position of the rigid body.
                btTransform     t;
                (*i)->getBody()->getMotionState()->getWorldTransform(t);
                // This test can not be done only once before the loop, since
                // it can happen that the kart falls through the track later!
                Vec3 to = t.getOrigin()+Vec3(0, -10000, 0);
                m_track->getTriangleMesh().castRay(t.getOrigin(), to, 
                                                   &hit_point, &material,
                                                   &normal);
                if(!material)
                {
                    fprintf(stderr, "ERROR: no valid starting position for kart %d on track %s.\n",
                            (int)(i-m_karts.begin()), m_track->getIdent().c_str());
                    if (UserConfigParams::m_artist_debug_mode)
                    {
                        fprintf(stderr, "Activating fly mode.\n");
                        (*i)->flyUp();
                        continue;
                    }
                    else
                    {
                        exit(-1);
                    }
                }
                all_finished=false;
                break;
            }
        }
    }   // while

    // Now store the current (i.e. in rest) suspension length for each kart,
    // so that the karts can visualise the suspension.
    for ( KartList::iterator i=m_karts.begin(); i!=m_karts.end(); i++)
        (*i)->setSuspensionLength();
    for(unsigned int i=0; i<m_karts.size(); i++)
        if(m_karts[i]->getCamera())
            m_karts[i]->getCamera()->setInitialTransform();
}   // resetAllKarts


void World::schedulePause(Phase phase)
{
    if (m_schedule_unpause)
    {
        m_schedule_unpause = false;
    }
    else
    {
        m_schedule_pause = true;
        m_scheduled_pause_phase = phase;
    }
}

void World::scheduleUnpause()
{
    if (m_schedule_pause)
    {
        m_schedule_pause = false;
    }
    else
    {
        m_schedule_unpause = true;
    }
}

//-----------------------------------------------------------------------------
/** This is the main interface to update the world. This function calls
 *  update(), and checks then for the end of the race. Note that race over 
 *  handling can not necessarily be done in update(), since not all
 *  data structures might have been updated (e.g.LinearWorld must
 *  call World::update() first, to get updated kart positions. If race
 *  over would be handled in World::update, LinearWorld had no opportunity
 *  to update its data structures before the race is finished).
 *  \param dt Time step size.
 */
void World::updateWorld(float dt)
{
    if (m_schedule_pause)
    {
        pause(m_scheduled_pause_phase);
        m_schedule_pause = false;
    }
    else if (m_schedule_unpause)
    {
        unpause();
        m_schedule_unpause = false;
    }
    
    // Don't update world if a menu is shown or the race is over.
    if( m_phase == FINISH_PHASE         ||
        m_phase == IN_GAME_MENU_PHASE      )  
        return;

    update(dt);
    if( (!isFinishPhase()) && isRaceOver())
    {
        enterRaceOverState();
    }

}   // updateWorld

#define MEASURE_FPS 0

//-----------------------------------------------------------------------------
/** Updates the physics, all karts, the track, and projectile manager.
 *  \param dt Time step size.
 */
void World::update(float dt)
{
#if MEASURE_FPS
    static float time = 0.0f;
    time += dt;
    if (time > 5.0f)
    {
        time -= 5.0f;
        printf("%i\n",irr_driver->getVideoDriver()->getFPS());
    }
#endif

    history->update(dt);
    if(history->replayHistory()) dt=history->getNextDelta();
    WorldStatus::update(dt);
    // Clear race state so that new information can be stored
    race_state->clear();

    if(network_manager->getMode()!=NetworkManager::NW_CLIENT &&
        !history->dontDoPhysics())
    {
        m_physics->update(dt);
    }

    const int kart_amount = m_karts.size();
    for (int i = 0 ; i < kart_amount; ++i)
    {
        // Update all karts that are not eliminated
        if(!m_karts[i]->isEliminated()) m_karts[i]->update(dt) ;
    }

    projectile_manager->update(dt);
}   // update

// ----------------------------------------------------------------------------
/** Only updates the track. The order in which the various parts of STK are 
 *  updated is quite important (i.e. the track can't be updated as part of
 *  the standard update call):
 *  the track must be updated after updating the karts (otherwise the
 *  checklines would be using the previous kart positions to determine
 *  new laps, but linear world which determines distance along track would
 *  be using the new kart positions --> the lap counting line will be 
 *  triggered one frame too late, potentially causing strange behaviour of
 *  the icons.
 *  Similarly linear world must update the position of all karts after all
 *  karts have been updated (i.e. World::update() must be called before
 *  updating the position of the karts). The check manager (which is called
 *  from Track::update()) needs the updated distance along track, so track
 *  update has to be called after updating the race position in linear world.
 *  That's why there is a separate call for trackUpdate here.
 */
void World::updateTrack(float dt)
{
    m_track->update(dt);
}   // update Track
// ----------------------------------------------------------------------------

Highscores* World::getHighscores() const
{
    if(!m_use_highscores) return NULL;

    const Highscores::HighscoreType type = "HST_" + getIdent();

    Highscores * highscores =
        highscore_manager->getHighscores(type,
                                         getNumKarts(),
                                         race_manager->getDifficulty(),
                                         race_manager->getTrackName(),
                                         race_manager->getNumLaps());

    return highscores;
}   // getHighscores

// ----------------------------------------------------------------------------
/** Called at the end of a race. Checks if the current times are worth a new
 *  score, if so it notifies the HighscoreManager so the new score is added 
 *  and saved.
 */
void World::updateHighscores(int* best_highscore_rank, int* best_finish_time, std::string* highscore_who,
                             StateManager::ActivePlayer** best_player)
{
    *best_highscore_rank = -1;
    *best_player = NULL;
    
    if(!m_use_highscores) return;
    
    // Add times to highscore list. First compute the order of karts,
    // so that the timing of the fastest kart is added first (otherwise
    // someone might get into the highscore list, only to be kicked out
    // again by a faster kart in the same race), which might be confusing
    // if we ever decide to display a message (e.g. during a race)
    unsigned int *index = new unsigned int[m_karts.size()];

    const unsigned int kart_amount = m_karts.size();
    for (unsigned int i=0; i<kart_amount; i++ )
    {
        index[i] = 999; // first reset the contents of the array
    }
    for (unsigned int i=0; i<kart_amount; i++ )
    {
        const int pos = m_karts[i]->getPosition()-1;
        if(pos < 0 || pos >= (int)kart_amount) continue; // wrong position
        index[pos] = i;
    }

    for (unsigned int pos=0; pos<kart_amount; pos++)
    {
        if(index[pos] == 999)
        {
            // no kart claimed to be in this position, most likely means
            // the kart location data is wrong

#ifdef DEBUG
            fprintf(stderr, "Error, incorrect kart positions:\n");
            for (unsigned int i=0; i<m_karts.size(); i++ )
            {
                fprintf(stderr, "i=%d position %d\n",i, m_karts[i]->getPosition());
            }
#endif
            continue;
        }

        // Only record times for player karts and only if they finished the race
        if(!m_karts[index[pos]]->getController()->isPlayerController()) continue;
        if (!m_karts[index[pos]]->hasFinishedRace()) continue;

        assert(index[pos] >= 0);
        assert(index[pos] < m_karts.size());
        Kart *k = (Kart*)m_karts[index[pos]];

        Highscores* highscores = getHighscores();

        PlayerController *controller = (PlayerController*)(k->getController());
        
        int highscore_rank = highscores->addData(k->getIdent(),
                                                 controller->getPlayer()->getProfile()->getName(),
                                                 k->getFinishTime());
        
        if (highscore_rank > 0)
        {
            if (*best_highscore_rank == -1 || highscore_rank < *best_highscore_rank)
            {
                *best_highscore_rank = highscore_rank;
                *best_finish_time = (int)(k->getFinishTime());
                *best_player = controller->getPlayer();
                *highscore_who = k->getIdent();
            }
            
            highscore_manager->saveHighscores();
        }
    } // next position
    delete []index;

}   // updateHighscores

//-----------------------------------------------------------------------------
/** Returns the n-th player kart. Note that this function is O(N), not O(1),
 *  so it shouldn't be called inside of loops.
 *  \param n Index of player kart to return.
 */
Kart *World::getPlayerKart(unsigned int n) const
{
    unsigned int count=-1;

    for(unsigned int i=0; i<m_karts.size(); i++)
        if(m_karts[i]->getController()->isPlayerController())
        {
            count++;
            if(count==n) return m_karts[i];
        }
    return NULL;
}   // getPlayerKart

//-----------------------------------------------------------------------------
/** Returns the nth local player kart, i.e. a player kart that has a camera.
 *  \param n Index of player kart to return.
 */
Kart *World::getLocalPlayerKart(unsigned int n) const
{
    int count=-1;
    const int kart_count = m_karts.size();
    for(int i=0; i<kart_count; i++)
    {
        if(m_karts[i]->getCamera() && m_karts[i]->getController()->isPlayerController())
        {
            count++;
            if(count == (int)n) return m_karts[i];
        }
    }
    return NULL;
}   // getLocalPlayerKart

//-----------------------------------------------------------------------------
/** Remove (eliminate) a kart from the race */
void World::removeKart(int kart_number, bool notifyOfElimination)
{
    Kart *kart = m_karts[kart_number];
    
    // Display a message about the eliminated kart in the race gui
    if (notifyOfElimination)
    {
        for (KartList::iterator i  = m_karts.begin(); i != m_karts.end();  i++ )
        {
            if(!(*i)->getCamera()) continue;
            if(*i==kart)
            {
                m_race_gui->addMessage(_("You have been eliminated!"), *i, 2.0f, 60);
            }
            else
            {
                m_race_gui->addMessage(_("'%s' has been eliminated.", core::stringw(kart->getName())),
                                       *i, 2.0f, 60);
            }
        }   // for i in kart
    }
    
    if(kart->getController()->isPlayerController())
    {
        // Change the camera so that it will be attached to the leader
        // and facing backwards.
        Camera* camera=kart->getCamera();
        camera->setMode(Camera::CM_LEADER_MODE);
        m_eliminated_players++;
    }

    // The kart can't be really removed from the m_kart array, since otherwise
    // a race can't be restarted. So it's only marked to be eliminated (and
    // ignored in all loops). Important:world->getCurrentNumKarts() returns
    // the number of karts still racing. This value can not be used for loops
    // over all karts, use race_manager->getNumKarts() instead!
    kart->eliminate();
    m_eliminated_karts++;

}   // removeKart

//-----------------------------------------------------------------------------
void World::getDefaultCollectibles(int& collectible_type, int& amount )
{
    collectible_type = PowerupManager::POWERUP_NOTHING;
    amount = 0;
}
//-----------------------------------------------------------------------------
void World::restartRace()
{
    // If m_saved_race_gui is set, it means that the restart was done
    // when the race result gui was being shown. In this case restore the 
    // race gui (not that the race result gui is cached and so never really
    // destroyed).
    if(m_saved_race_gui)
    {
        m_race_gui       = m_saved_race_gui;
        m_saved_race_gui = NULL;
    }

    m_race_gui->clearAllMessages();

    m_schedule_pause = false;
    m_schedule_unpause = false;
    
    WorldStatus::reset();
    m_faster_music_active = false;
    m_eliminated_karts    = 0;
    m_eliminated_players  = 0;
    
    for ( KartList::iterator i = m_karts.begin(); i != m_karts.end() ; ++i )
    {
        (*i)->reset();
    }

    resetAllKarts();

    // Start music from beginning
    music_manager->stopMusic();
    m_track->reset();

    // Enable SFX again
    sfx_manager->resumeAll();

    projectile_manager->cleanup();
    race_manager->reset();
}   // restartRace

//-----------------------------------------------------------------------------
/** Pauses the music (and then pauses WorldStatus).
 */
void World::pause(Phase phase)
{
    music_manager->pauseMusic();
    sfx_manager->pauseAll();
    
    WorldStatus::pause(phase);
}   // pause

//-----------------------------------------------------------------------------
void World::unpause()
{
    music_manager->resumeMusic() ;
    sfx_manager->resumeAll();
    
    WorldStatus::unpause();
    
    for(unsigned int i=0; i<m_karts.size(); i++)
    {
        // Note that we can not test for isPlayerController here, since
        // an EndController will also return 'isPlayerController' if the
        // kart belonged to a player.
        PlayerController *pc = dynamic_cast<PlayerController*>(m_karts[i]->getController());
        if(pc)
            pc->resetInputState();
    }
}   // pause

/* EOF */
