stk-code_catmod/src/race/grand_prix_data.cpp
2018-06-03 21:48:34 -04:00

613 lines
21 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2004-2015 Ingo Ruhnke <grumbel@gmx.de>
// Copyright (C) 2006-2015 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 "config/user_config.hpp"
#include "challenges/unlock_manager.hpp"
#include "config/player_manager.hpp"
#include "io/file_manager.hpp"
#include "io/utf_writer.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>
// ----------------------------------------------------------------------------
/** Loads a grand prix definition from a file.
* \param filename Name of the file to load.
*/
GrandPrixData::GrandPrixData(const std::string& filename, enum GPGroupType group)
{
setFilename(filename);
m_id = StringUtils::getBasename(StringUtils::removeExtension(filename));
m_editable = (filename.find(file_manager->getGPDir(), 0) == 0);
m_group = group;
reload();
} // GrandPrixData
// ----------------------------------------------------------------------------
/** Creates a random grand prix from the specified parameters.
* \param number_of_tracks How many tracks to select.
* \param track_group From which track group to select the tracks.
* \param use_reverse How the reverse setting is to be determined.
* \param new_tracks If true, new tracks are selected, otherwise existing
* tracks will not be changed (used to e.g. increase the number of
* tracks in an already existing random grand prix).
*
*/
void GrandPrixData::createRandomGP(const unsigned int number_of_tracks,
const std::string &track_group,
const GPReverseType use_reverse,
bool new_tracks)
{
m_filename = "Random GP - Not loaded from a file!";
m_id = getRandomGPID();
m_name = getRandomGPName();
m_editable = false;
m_group = GP_NONE;
m_reverse_type = use_reverse;
if(new_tracks)
{
m_tracks.clear();
m_laps.clear();
m_reversed.clear();
}
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);
} // createRandomGP
// ----------------------------------------------------------------------------
/** Either adds or removes tracks to get the requested numder of tracks in
* a random GP.
* \param number_of_tracks How many tracks should be in the random list.
* \param track_group From which group to select the tracks.
*/
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;
if (track_group == "all")
{
for(unsigned int i=0; i<track_manager->getNumberOfTracks(); i++)
{
const Track *track = track_manager->getTrack(i);
// Ignore no-racing tracks:
if(!track->isRaceTrack())
continue;
if (PlayerManager::getCurrentPlayer()->isLocked(track->getIdent()))
continue;
// Only add tracks that are not already picked.
if(std::find(m_tracks.begin(), m_tracks.end(), track->getIdent())==
m_tracks.end())
track_indices.push_back(i);
}
}
else
{
track_indices = track_manager->getTracksInGroup(track_group);
}
assert(number_of_tracks <= track_indices.size() + m_tracks.size());
// add or remove the right number of tracks
if (m_tracks.size() < number_of_tracks)
{
while (m_tracks.size() < number_of_tracks)
{
int index = rand() % track_indices.size();
int track_index = track_indices[index];
const Track *track = track_manager->getTrack(track_index);
std::string id = track->getIdent();
if (PlayerManager::getCurrentPlayer()->isLocked(track->getIdent()))
continue;
bool is_already_added = false;
for (unsigned int i = 0; i < m_tracks.size(); i++)
{
if (m_tracks[i] == id)
{
is_already_added = true;
break;
}
}
if (!is_already_added)
{
m_tracks.push_back(id);
m_laps.push_back(track->getDefaultNumberOfLaps());
m_reversed.push_back(false); // This will be changed later in the code
}
track_indices.erase(track_indices.begin()+index);
}
}
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());
} // changeTrackNumber
// ----------------------------------------------------------------------------
/** Updates the GP data with newly decided reverse requirements.
* \param use_reverse How reverse setting for each track is to be determined.
*/
void GrandPrixData::changeReverse(const GrandPrixData::GPReverseType use_reverse)
{
m_reverse_type = use_reverse;
for (unsigned int i = 0; i < m_tracks.size(); i++)
{
if (use_reverse == GP_NO_REVERSE)
{
m_reversed[i] = false;
}
else if (use_reverse == GP_ALL_REVERSE) // all reversed
{
m_reversed[i] = track_manager->getTrack(m_tracks[i])->reverseAvailable();
}
else if (use_reverse == GP_RANDOM_REVERSE)
{
if (track_manager->getTrack(m_tracks[i])->reverseAvailable())
m_reversed[i] = (rand() % 2 != 0);
else
m_reversed[i] = false;
}
} // for i < m_tracks.size()
} // changeReverse
// ----------------------------------------------------------------------------
/** Sets the id of this grand prix.
* \param id The new id.
*/
void GrandPrixData::setId(const std::string& id)
{
m_id = id;
} // setId
// ----------------------------------------------------------------------------
/** Sets the name of the grand prix.
* \param name New name.
*/
void GrandPrixData::setName(const irr::core::stringw& name)
{
m_name = name;
} // setName
// ----------------------------------------------------------------------------
/** Sets the filename of this grand prix.
* \param filename New filename.
*/
void GrandPrixData::setFilename(const std::string& filename)
{
m_filename = filename;
} // setFilename
// ----------------------------------------------------------------------------
/** Sets if this grand prix can be edited.
* \param editable New value.
*/
void GrandPrixData::setEditable(const bool editable)
{
m_editable = editable;
} // setEditable
// ----------------------------------------------------------------------------
/** Sets the group of this grand prix.
* \param editable New value.
*/
void GrandPrixData::setGroup(const enum GPGroupType group)
{
m_group = group;
} // setGroup
// ----------------------------------------------------------------------------
/** Reloads grand prix from file.
*/
void GrandPrixData::reload()
{
m_tracks.clear();
m_laps.clear();
m_reversed.clear();
std::unique_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->getAndDecode("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::warn("GrandPrixData",
"Grandprix file '%s': There is no track defined",
m_filename.c_str());
}
// 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 && !UserConfigParams::m_artist_debug_mode)
{
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()
// ----------------------------------------------------------------------------
/** Saves the grand prix data to a file.
*/
bool GrandPrixData::writeToFile()
{
try
{
UTFWriter file(m_filename.c_str(), false);
if (file.is_open())
{
file << "\n<supertuxkart_grand_prix name=\"" << StringUtils::xmlEncode(m_name)
<< "\">\n\n";
for (unsigned int i = 0; i < m_tracks.size(); i++)
{
file <<
"\t<track id=\"" << m_tracks[i] <<
"\" laps=\"" << m_laps[i] <<
"\" reverse=\"" << (m_reversed[i] ? L"true" : L"false")
<< "\" />\n";
}
file << "\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;
}
} // writeToFile
// ----------------------------------------------------------------------------
/** Checks if the grand prix data are consistent.
* \param log_error: If errors should be sent to the logger.
*/
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;
} // checkConsistency
// ----------------------------------------------------------------------------
/** 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
* \param id Name of the track to test.
* \param include_locked If set to true, all tracks (including locked tracks)
* are considered to be available.
*/
bool GrandPrixData::isTrackAvailable(const std::string &id,
bool include_locked ) const
{
if (include_locked)
return true;
else if (id == "fortmagma")
return !PlayerManager::getCurrentPlayer()->isLocked("fortmagma");
else
return (!m_editable ||
!PlayerManager::get()->getCurrentPlayer()->isLocked(id));
} // isTrackAvailable
// ----------------------------------------------------------------------------
/** Returns the list of tracks that is available (i.e. unlocked) of this
* grand prix.
* \param include_locked If data for locked tracks should be included or not.
* \return A copy of the list of available tracks in this grand prix.
*/
std::vector<std::string> GrandPrixData::getTrackNames(bool include_locked) const
{
std::vector<std::string> names;
for (unsigned int i = 0; i < m_tracks.size(); i++)
{
if(isTrackAvailable(m_tracks[i], include_locked))
names.push_back(m_tracks[i]);
}
return names;
} // getTrackNames
// ----------------------------------------------------------------------------
/** Returns the laps for each available track of the grand prix.
* \param include_locked If data for locked tracks should be included or not.
* \return a std::vector containing the laps for each grand prix.
*/
std::vector<int> GrandPrixData::getLaps(bool include_locked) const
{
std::vector<int> laps;
for (unsigned int i = 0; i< m_tracks.size(); i++)
if(isTrackAvailable(m_tracks[i], include_locked))
laps.push_back(m_laps[i]);
return laps;
} // getLaps
// ----------------------------------------------------------------------------
/** Returns the reverse setting for each available grand prix.
* \param include_locked If data for locked tracks should be included or not.
* \return A copy of alist with the reverse status for each track.
*/
std::vector<bool> GrandPrixData::getReverse(bool include_locked) const
{
std::vector<bool> reverse;
for (unsigned int i = 0; i< m_tracks.size(); i++)
if(isTrackAvailable(m_tracks[i], include_locked))
reverse.push_back(m_reversed[i]);
return reverse;
} // getReverse
// ----------------------------------------------------------------------------
/** Returns true if this grand prix can be edited.
*/
bool GrandPrixData::isEditable() const
{
return m_editable;
} // isEditable
// ----------------------------------------------------------------------------
/** Returns the number of tracks in this grand prix.
* \param include_locked If data for locked tracks should be included or not.
*/
unsigned int GrandPrixData::getNumberOfTracks(bool includeLocked) const
{
if (includeLocked)
return (unsigned int)m_tracks.size();
else
return (unsigned int)getTrackNames(false).size();
} // getNumberOfTracks
// ----------------------------------------------------------------------------
/** Returns the (translated) name of the track with the specified index.
*/
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();
} // getTrackName
// ----------------------------------------------------------------------------
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 */