stk-code_catmod/src/race/grand_prix_data.cpp
2014-07-15 15:46:17 +02:00

501 lines
17 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2004-2013 Ingo Ruhnke <grumbel@gmx.de>
// Copyright (C) 2006-2013 Joerg Henrichs
//
// 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 "race/grand_prix_data.hpp"
#include "config/player_profile.hpp"
#include "challenges/unlock_manager.hpp"
#include "config/player_manager.hpp"
#include "io/file_manager.hpp"
#include "io/utf_writer.hpp"
#include "states_screens/dialogs/random_gp_dialog.hpp"
#include "tracks/track_manager.hpp"
#include "tracks/track.hpp"
#include "utils/string_utils.hpp"
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <memory>
#include <stdexcept>
// ----------------------------------------------------------------------------
GrandPrixData::GrandPrixData(const std::string& filename)
{
m_filename = filename;
m_id = StringUtils::getBasename(
StringUtils::removeExtension(filename));
m_editable = (filename.find(file_manager->getGPDir(), 0) == 0);
reload();
}
// ----------------------------------------------------------------------------
GrandPrixData::GrandPrixData(const unsigned int number_of_tracks,
const std::string& track_group,
const RandomGPInfoDialog::REVERSED use_reverse)
{
m_filename = "Random GP - Not loaded from a file!";
m_id = "random";
m_name = "Random Grand Prix";
m_editable = false;
m_tracks.reserve(number_of_tracks);
m_laps.reserve(number_of_tracks);
m_reversed.reserve(number_of_tracks);
changeTrackNumber(number_of_tracks, track_group);
changeReverse(use_reverse);
}
// ----------------------------------------------------------------------------
void GrandPrixData::changeTrackNumber(const unsigned int number_of_tracks,
const std::string& track_group)
{
// The problem with the track groups is that "all" isn't a track group
// TODO: Add "all" to the track groups and rewrite this more elegant
std::vector<int> track_indices;
size_t available_tracks;
if (track_group == "all")
{
available_tracks = track_manager->getNumberOfTracks();
}
else
{
track_indices = track_manager->getTracksInGroup(track_group);
available_tracks = track_indices.size();
}
assert(number_of_tracks <= available_tracks);
// add or remove the right number of tracks
if (m_tracks.size() < number_of_tracks)
{
while (m_tracks.size() < number_of_tracks)
{
int index = (track_group == "all") ?
rand() % available_tracks :
track_indices[rand() % available_tracks];
std::string id = track_manager->getTrack(index)->getIdent();
// Avoid duplicate tracks
if (std::find(m_tracks.begin(), m_tracks.end(), id) != m_tracks.end())
continue;
m_tracks.push_back(id);
m_laps.push_back(track_manager->getTrack(index)->getDefaultNumberOfLaps());
m_reversed.push_back(false); // This will be changed later
}
}
else if (m_tracks.size() > number_of_tracks)
{
while (m_tracks.size() > number_of_tracks)
{
m_tracks.pop_back();
m_laps.pop_back();
m_reversed.pop_back();
}
}
assert(m_tracks.size() == m_laps.size() );
assert(m_laps.size() == m_reversed.size());
}
// ----------------------------------------------------------------------------
void GrandPrixData::changeReverse(const RandomGPInfoDialog::REVERSED use_reverse)
{
for (unsigned int i = 0; i < m_tracks.size(); i++)
{
if (use_reverse == RandomGPInfoDialog::NO_REVERSE)
m_reversed[i] = false;
else if (use_reverse == RandomGPInfoDialog::MIXED)
if (track_manager->getTrack(m_tracks[i])->reverseAvailable())
m_reversed[i] = (rand() % 2 != 0);
else
m_reversed[i] = false;
else // all reversed
m_reversed[i] = track_manager->getTrack(m_tracks[i])->reverseAvailable();
}
}
// ----------------------------------------------------------------------------
void GrandPrixData::setId(const std::string& id)
{
m_id = id;
}
// ----------------------------------------------------------------------------
void GrandPrixData::setName(const irr::core::stringw& name)
{
m_name = name;
}
// ----------------------------------------------------------------------------
void GrandPrixData::setFilename(const std::string& filename)
{
m_filename = filename;
}
// ----------------------------------------------------------------------------
void GrandPrixData::setEditable(const bool editable)
{
m_editable = editable;
}
// ----------------------------------------------------------------------------
void GrandPrixData::reload()
{
m_tracks.clear();
m_laps.clear();
m_reversed.clear();
std::auto_ptr<XMLNode> root(file_manager->createXMLTree(m_filename));
if (root.get() == NULL)
{
Log::error("GrandPrixData",
"Error while trying to read xml Grand Prix from file '%s'. "
"Is the file readable for supertuxkart?",
m_filename.c_str());
throw std::runtime_error("File couldn't be read");
}
if (root->getName() != "supertuxkart_grand_prix")
{
Log::error("GrandPrixData",
"Error while trying to read Grand Prix file '%s': "
"Root node has the wrong name %s", m_filename.c_str(),
root->getName().c_str());
throw std::runtime_error("Wrong root node name");
}
if (!root->get("name", &m_name))
{
Log::error("GrandPrixData",
"Error while trying to read grandprix file '%s': "
"Missing 'name' attribute", m_filename.c_str());
throw std::runtime_error("Missing name attribute");
}
const int amount = root->getNumNodes();
if (amount == 0)
{
Log::error("GrandPrixData",
"Error while trying to read grandprix file '%s': "
"There is no track defined", m_filename.c_str());
throw std::runtime_error("No track defined");
}
// Every iteration means parsing one track entry
for (int i = 0; i < amount; i++)
{
const XMLNode* node = root->getNode(i);
if (node->getName() != "track")
{
Log::error("GrandPrixData"
"Unknown node in Grand Prix XML file '%s': %s",
m_filename.c_str(), node->getName().c_str());
throw std::runtime_error("Unknown node in the XML file");
}
// 1. Parsing the id atttribute
std::string track_id;
if (!node->get("id", &track_id))
{
Log::error("GrandPrixData",
"The id attribute is missing in the %d. track entry of "
"the Grand Prix file '%s'.", i, m_filename.c_str());
throw std::runtime_error("Missing track id");
}
// 1.1 Checking if the track exists
Track* t = track_manager->getTrack(track_id);
if (t == NULL)
{
Log::error("GrandPrixData",
"The Grand Prix file '%s' contains a track '%s' that "
"does not exist", m_filename.c_str(), track_id.c_str());
throw std::runtime_error("Unknown track");
}
// 2. Parsing the number of laps
int number_of_laps;
if (!node->get("laps", &number_of_laps))
{
Log::error("GrandPrixData",
"The laps attribute is missing in the %d. track entry "
"of the Grand Prix file '%s'.", i, m_filename.c_str());
throw std::runtime_error("Missing track id");
}
if (number_of_laps < 1)
{
Log::error("GrandPrixData",
"Track '%s' in the Grand Prix file '%s' should be raced "
"with %d laps, which isn't possible.", track_id.c_str(),
m_filename.c_str());
throw std::runtime_error("Lap count lower than 1");
}
// 3. Parsing the reversed attribute
bool reversed = false; // Stays false if not found
node->get("reverse", &reversed );
if (!t->reverseAvailable())
reversed = false;
// Adding parsed data
m_tracks.push_back(track_id);
m_laps.push_back(number_of_laps);
m_reversed.push_back(reversed);
assert(m_tracks.size() == m_laps.size() );
assert(m_laps.size() == m_reversed.size());
} // end for all root nodes
} // reload()
// ----------------------------------------------------------------------------
bool GrandPrixData::writeToFile()
{
try
{
UTFWriter file(m_filename.c_str());
if (file.is_open())
{
file << L"\n<supertuxkart_grand_prix name=\"" << m_name
<< L"\">\n\n";
for (unsigned int i = 0; i < m_tracks.size(); i++)
{
file <<
L"\t<track id=\"" << m_tracks[i] <<
L"\" laps=\"" << m_laps[i] <<
L"\" reverse=\"" << (m_reversed[i] ? L"true" : L"false")
<< L"\" />\n";
}
file << L"\n</supertuxkart_grand_prix>\n";
file.close();
return true;
}
return false;
}
catch (std::runtime_error& e)
{
Log::error("GrandPrixData",
"Failed to write grand prix to '%s'; cause: %s",
m_filename.c_str(), e.what());
return false;
}
}
// ----------------------------------------------------------------------------
bool GrandPrixData::checkConsistency(bool log_error) const
{
for (unsigned int i = 0; i < m_tracks.size(); i++)
{
if (track_manager->getTrack(m_tracks[i]) == NULL)
{
if (log_error)
{
Log::error("GrandPrixData",
"The grand prix '%ls' won't be available because "
"the track '%s' does not exist!", m_name.c_str(),
m_tracks[i].c_str());
}
return false;
}
}
return true;
}
// ----------------------------------------------------------------------------
/** Returns true if the track is available. This is used to test if Fort Magma
* is available (this way FortMagma is not used in the last Grand Prix in
* story mode, but will be available once all challenges are done and nolok
* is unlocked). It also prevents people from using the grand prix editor as
* a way to play tracks that still haven't been unlocked
*/
bool GrandPrixData::isTrackAvailable(const std::string &id,
bool includeLocked ) const
{
if (includeLocked)
return true;
else if (id == "fortmagma")
return !PlayerManager::getCurrentPlayer()->isLocked("fortmagma");
else
return (!m_editable ||
!PlayerManager::get()->getCurrentPlayer()->isLocked(id));
}
// ----------------------------------------------------------------------------
std::vector<std::string> GrandPrixData::getTrackNames(bool includeLocked) const
{
std::vector<std::string> names;
for (unsigned int i = 0; i < m_tracks.size(); i++)
{
if(isTrackAvailable(m_tracks[i], includeLocked))
names.push_back(m_tracks[i]);
}
return names;
}
// ----------------------------------------------------------------------------
std::vector<int> GrandPrixData::getLaps(bool includeLocked) const
{
std::vector<int> laps;
for (unsigned int i = 0; i< m_tracks.size(); i++)
if(isTrackAvailable(m_tracks[i], includeLocked))
laps.push_back(m_laps[i]);
return laps;
}
// ----------------------------------------------------------------------------
std::vector<bool> GrandPrixData::getReverse(bool includeLocked) const
{
std::vector<bool> reverse;
for (unsigned int i = 0; i< m_tracks.size(); i++)
if(isTrackAvailable(m_tracks[i], includeLocked))
reverse.push_back(m_reversed[i]);
return reverse;
}
// ----------------------------------------------------------------------------
bool GrandPrixData::isEditable() const
{
return m_editable;
}
// ----------------------------------------------------------------------------
unsigned int GrandPrixData::getNumberOfTracks(bool includeLocked) const
{
if (includeLocked)
return m_tracks.size();
else
return getTrackNames(false).size();
}
// ----------------------------------------------------------------------------
irr::core::stringw GrandPrixData::getTrackName(const unsigned int track) const
{
assert(track < getNumberOfTracks(true));
Track* t = track_manager->getTrack(m_tracks[track]);
assert(t != NULL);
return t->getName();
}
// ----------------------------------------------------------------------------
const std::string& GrandPrixData::getTrackId(const unsigned int track) const
{
assert(track < getNumberOfTracks(true));
return m_tracks[track];
}
// ----------------------------------------------------------------------------
unsigned int GrandPrixData::getLaps(const unsigned int track) const
{
assert(track < getNumberOfTracks(true));
return m_laps[track];
}
// ----------------------------------------------------------------------------
bool GrandPrixData::getReverse(const unsigned int track) const
{
assert(track < getNumberOfTracks(true));
return m_reversed[track];
}
// ----------------------------------------------------------------------------
void GrandPrixData::moveUp(const unsigned int track)
{
assert (track > 0 && track < getNumberOfTracks(true));
std::swap(m_tracks[track], m_tracks[track - 1]);
std::swap(m_laps[track], m_laps[track - 1]);
bool tmp = m_reversed[track ];
m_reversed[track] = m_reversed[track - 1];
m_reversed[track - 1] = tmp;
}
// ----------------------------------------------------------------------------
void GrandPrixData::moveDown(const unsigned int track)
{
assert (track < (getNumberOfTracks(true) - 1));
std::swap(m_tracks[track], m_tracks[track + 1]);
std::swap(m_laps[track], m_laps[track + 1]);
bool tmp = m_reversed[track ];
m_reversed[track ] = m_reversed[track + 1];
m_reversed[track + 1] = tmp;
}
// ----------------------------------------------------------------------------
void GrandPrixData::addTrack(Track* track, unsigned int laps, bool reverse,
int position)
{
int n = getNumberOfTracks(true);
assert (track != NULL);
assert (laps > 0);
assert (-1 <= position && position < n);
if (position < 0 || position == (n - 1) || m_tracks.empty())
{
// Append new track to the end of the list
m_tracks.push_back(track->getIdent());
m_laps.push_back(laps);
m_reversed.push_back(reverse);
}
else
{
// Insert new track right after the specified position. Caution:
// std::vector inserts elements _before_ the specified position
m_tracks. insert(m_tracks.begin() + position + 1, track->getIdent());
m_laps. insert(m_laps.begin() + position + 1, laps );
m_reversed.insert(m_reversed.begin() + position + 1, reverse );
}
}
// ----------------------------------------------------------------------------
void GrandPrixData::editTrack(unsigned int index, Track* track,
unsigned int laps, bool reverse)
{
assert (index < getNumberOfTracks(true));
assert (track != NULL);
assert (laps > 0);
m_tracks[index] = track->getIdent();
m_laps[index] = laps;
m_reversed[index] = reverse;
}
// ----------------------------------------------------------------------------
void GrandPrixData::remove(const unsigned int track)
{
assert (0 < track && track < getNumberOfTracks(true));
m_tracks.erase(m_tracks.begin() + track);
m_laps.erase(m_laps.begin() + track);
m_reversed.erase(m_reversed.begin() + track);
}
/* EOF */