support loading of course edits
This commit is contained in:
+2
-429
@@ -8,7 +8,6 @@
|
||||
#include "GameState.h"
|
||||
#include "RageException.h"
|
||||
#include "RageLog.h"
|
||||
#include "MsdFile.h"
|
||||
#include "PlayerOptions.h"
|
||||
#include "SongOptions.h"
|
||||
#include "SongCacheIndex.h"
|
||||
@@ -22,6 +21,7 @@
|
||||
#include "Foreach.h"
|
||||
#include "UnlockManager.h"
|
||||
#include <limits.h>
|
||||
#include "CourseLoaderCRS.h"
|
||||
|
||||
|
||||
static const CString CourseTypeNames[] = {
|
||||
@@ -124,306 +124,12 @@ PlayMode Course::GetPlayMode() const
|
||||
}
|
||||
}
|
||||
|
||||
void Course::LoadFromCRSFile( CString sPath )
|
||||
{
|
||||
Init();
|
||||
|
||||
m_sPath = sPath; // save path
|
||||
|
||||
// save group name
|
||||
{
|
||||
CStringArray parts;
|
||||
split( sPath, "/", parts, false );
|
||||
if( parts.size() >= 4 ) // e.g. "/Courses/blah/fun.cvs"
|
||||
m_sGroupName = parts[parts.size()-2];
|
||||
}
|
||||
|
||||
|
||||
bool bUseCache = true;
|
||||
{
|
||||
/* First look in the cache for this course. Don't bother
|
||||
* honoring FastLoad for checking the course hash, since
|
||||
* courses are normally grouped into a few directories, not
|
||||
* one directory per course. XXX: if !FastLoad, regen
|
||||
* cache if the used songs have changed */
|
||||
unsigned uHash = SONGINDEX->GetCacheHash( m_sPath );
|
||||
if( !DoesFileExist(GetCacheFilePath()) )
|
||||
bUseCache = false;
|
||||
if( !PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sPath) != uHash )
|
||||
bUseCache = false; // this cache is out of date
|
||||
}
|
||||
|
||||
if( bUseCache )
|
||||
{
|
||||
CString sCacheFile = GetCacheFilePath();
|
||||
LOG->Trace( "Course::LoadFromCRSFile(\"%s\") (\"%s\")", sPath.c_str(), sCacheFile.c_str() );
|
||||
sPath = sCacheFile.c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Trace( "Course::LoadFromCRSFile(\"%s\")", sPath.c_str() );
|
||||
}
|
||||
|
||||
MsdFile msd;
|
||||
if( !msd.ReadFile(sPath) )
|
||||
RageException::Throw( "Error opening CRS file '%s'.", sPath.c_str() );
|
||||
|
||||
const CString sFName = SetExtension( m_sPath, "" );
|
||||
|
||||
CStringArray arrayPossibleBanners;
|
||||
GetDirListing( sFName + "*.png", arrayPossibleBanners, false, true );
|
||||
GetDirListing( sFName + "*.jpg", arrayPossibleBanners, false, true );
|
||||
GetDirListing( sFName + "*.bmp", arrayPossibleBanners, false, true );
|
||||
GetDirListing( sFName + "*.gif", arrayPossibleBanners, false, true );
|
||||
if( !arrayPossibleBanners.empty() )
|
||||
m_sBannerPath = arrayPossibleBanners[0];
|
||||
|
||||
AttackArray attacks;
|
||||
float fGainSeconds = 0;
|
||||
for( unsigned i=0; i<msd.GetNumValues(); i++ )
|
||||
{
|
||||
CString sValueName = msd.GetParam(i, 0);
|
||||
const MsdFile::value_t &sParams = msd.GetValue(i);
|
||||
|
||||
// handle the data
|
||||
if( 0 == stricmp(sValueName, "COURSE") )
|
||||
m_sMainTitle = sParams[1];
|
||||
else if( 0 == stricmp(sValueName, "COURSETRANSLIT") )
|
||||
m_sMainTitleTranslit = sParams[1];
|
||||
else if( 0 == stricmp(sValueName, "REPEAT") )
|
||||
{
|
||||
CString str = sParams[1];
|
||||
str.MakeLower();
|
||||
if( str.Find("yes") != -1 )
|
||||
m_bRepeat = true;
|
||||
}
|
||||
|
||||
else if( 0 == stricmp(sValueName, "LIVES") )
|
||||
m_iLives = atoi( sParams[1] );
|
||||
|
||||
else if( 0 == stricmp(sValueName, "GAINSECONDS") )
|
||||
fGainSeconds = strtof( sParams[1], NULL );
|
||||
|
||||
else if( 0 == stricmp(sValueName, "METER") )
|
||||
{
|
||||
if( sParams.params.size() == 2 )
|
||||
m_iCustomMeter[DIFFICULTY_MEDIUM] = atoi( sParams[1] ); /* compat */
|
||||
else if( sParams.params.size() == 3 )
|
||||
{
|
||||
const CourseDifficulty cd = StringToCourseDifficulty( sParams[1] );
|
||||
if( cd == DIFFICULTY_INVALID )
|
||||
{
|
||||
LOG->Warn( "Course file '%s' contains an invalid #METER string: \"%s\"",
|
||||
sPath.c_str(), sParams[1].c_str() );
|
||||
continue;
|
||||
}
|
||||
m_iCustomMeter[cd] = atoi( sParams[2] );
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0 == stricmp(sValueName, "MODS") )
|
||||
{
|
||||
Attack attack;
|
||||
float end = -9999;
|
||||
for( unsigned j = 1; j < sParams.params.size(); ++j )
|
||||
{
|
||||
CStringArray sBits;
|
||||
split( sParams[j], "=", sBits, false );
|
||||
if( sBits.size() < 2 )
|
||||
continue;
|
||||
|
||||
TrimLeft( sBits[0] );
|
||||
TrimRight( sBits[0] );
|
||||
if( !sBits[0].CompareNoCase("TIME") )
|
||||
attack.fStartSecond = strtof( sBits[1], NULL );
|
||||
else if( !sBits[0].CompareNoCase("LEN") )
|
||||
attack.fSecsRemaining = strtof( sBits[1], NULL );
|
||||
else if( !sBits[0].CompareNoCase("END") )
|
||||
end = strtof( sBits[1], NULL );
|
||||
else if( !sBits[0].CompareNoCase("MODS") )
|
||||
{
|
||||
attack.sModifiers = sBits[1];
|
||||
if( end != -9999 )
|
||||
{
|
||||
ASSERT_M( end >= attack.fStartSecond, ssprintf("Attack ends before it starts. end %f, start %f", end, attack.fStartSecond) );
|
||||
attack.fSecsRemaining = end - attack.fStartSecond;
|
||||
end = -9999;
|
||||
}
|
||||
|
||||
// warn on invalid so we catch bogus mods on load
|
||||
PlayerOptions po;
|
||||
po.FromString( attack.sModifiers, true );
|
||||
|
||||
attacks.push_back( attack );
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Warn( "Unexpected value named '%s'", sBits[0].c_str() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if( 0 == stricmp(sValueName, "SONG") )
|
||||
{
|
||||
CourseEntry new_entry;
|
||||
|
||||
// infer entry::Type from the first param
|
||||
if( sParams[1].Left(strlen("BEST")) == "BEST" )
|
||||
{
|
||||
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1;
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_MostPlays;
|
||||
}
|
||||
else if( sParams[1].Left(strlen("WORST")) == "WORST" )
|
||||
{
|
||||
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1;
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_FewestPlays;
|
||||
}
|
||||
else if( sParams[1] == "*" )
|
||||
{
|
||||
new_entry.bSecret = true;
|
||||
}
|
||||
else if( sParams[1].Right(1) == "*" )
|
||||
{
|
||||
new_entry.bSecret = true;
|
||||
CString sSong = sParams[1];
|
||||
sSong.Replace( "\\", "/" );
|
||||
CStringArray bits;
|
||||
split( sSong, "/", bits );
|
||||
if( bits.size() == 2 )
|
||||
{
|
||||
new_entry.sSongGroup = bits[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Warn( "Course file '%s' contains a random_within_group entry '%s' that is invalid. "
|
||||
"Song should be in the format '<group>/*'.",
|
||||
sPath.c_str(), sSong.c_str());
|
||||
}
|
||||
|
||||
if( !SONGMAN->DoesSongGroupExist(new_entry.sSongGroup) )
|
||||
{
|
||||
/* XXX: We need a place to put "user warnings". This is too loud for info.txt--
|
||||
* it obscures important warnings--and regular users never look there, anyway. */
|
||||
LOG->Trace( "Course file '%s' random_within_group entry '%s' specifies a group that doesn't exist. "
|
||||
"This entry will be ignored.",
|
||||
sPath.c_str(), sSong.c_str());
|
||||
continue; // skip this #SONG
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CString sSong = sParams[1];
|
||||
new_entry.pSong = SONGMAN->FindSong( sSong );
|
||||
|
||||
if( new_entry.pSong == NULL )
|
||||
{
|
||||
/* XXX: We need a place to put "user warnings". This is too loud for info.txt--
|
||||
* it obscures important warnings--and regular users never look there, anyway. */
|
||||
LOG->Trace( "Course file '%s' contains a fixed song entry '%s' that does not exist. "
|
||||
"This entry will be ignored.",
|
||||
sPath.c_str(), sSong.c_str());
|
||||
continue; // skip this #SONG
|
||||
}
|
||||
}
|
||||
|
||||
new_entry.baseDifficulty = StringToDifficulty( sParams[2] );
|
||||
if( new_entry.baseDifficulty == DIFFICULTY_INVALID )
|
||||
{
|
||||
int retval = sscanf( sParams[2], "%d..%d", &new_entry.iLowMeter, &new_entry.iHighMeter );
|
||||
if( retval == 1 )
|
||||
new_entry.iHighMeter = new_entry.iLowMeter;
|
||||
else if( retval != 2 )
|
||||
{
|
||||
LOG->Warn("Course file '%s' contains an invalid difficulty setting: \"%s\", 3..6 used instead",
|
||||
sPath.c_str(), sParams[2].c_str());
|
||||
new_entry.iLowMeter = 3;
|
||||
new_entry.iHighMeter = 6;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
/* If "showcourse" or "noshowcourse" is in the list, force new_entry.secret
|
||||
* on or off. */
|
||||
CStringArray mods;
|
||||
split( sParams[3], ",", mods, true );
|
||||
for( int j = (int) mods.size()-1; j >= 0 ; --j )
|
||||
{
|
||||
CString &sMod = mods[j];
|
||||
TrimLeft( sMod );
|
||||
TrimRight( sMod );
|
||||
if( !sMod.CompareNoCase("showcourse") )
|
||||
new_entry.bSecret = false;
|
||||
else if( !sMod.CompareNoCase("noshowcourse") )
|
||||
new_entry.bSecret = true;
|
||||
else if( !sMod.CompareNoCase("nodifficult") )
|
||||
new_entry.bNoDifficult = true;
|
||||
else
|
||||
continue;
|
||||
mods.erase(mods.begin() + j);
|
||||
}
|
||||
new_entry.sModifiers = join( ",", mods );
|
||||
}
|
||||
|
||||
new_entry.attacks = attacks;
|
||||
new_entry.fGainSeconds = fGainSeconds;
|
||||
attacks.clear();
|
||||
|
||||
m_vEntries.push_back( new_entry );
|
||||
}
|
||||
else if( bUseCache && !stricmp(sValueName, "RADAR") )
|
||||
{
|
||||
StepsType st = (StepsType) atoi(sParams[1]);
|
||||
CourseDifficulty cd = (CourseDifficulty) atoi(sParams[2]);
|
||||
|
||||
RadarValues rv;
|
||||
rv.FromString( sParams[3] );
|
||||
m_RadarCache[CacheEntry(st, cd)] = rv;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Warn( "Unexpected value named '%s'", sValueName.c_str() );
|
||||
}
|
||||
}
|
||||
static TitleSubst tsub("courses");
|
||||
|
||||
TitleFields title;
|
||||
title.Title = m_sMainTitle;
|
||||
title.TitleTranslit = m_sMainTitleTranslit;
|
||||
tsub.Subst( title );
|
||||
m_sMainTitle = title.Title;
|
||||
m_sMainTitleTranslit = title.TitleTranslit;
|
||||
|
||||
/* Cache and load the course banner. Only bother doing this if at least one
|
||||
* song was found in the course. */
|
||||
if( m_sBannerPath != "" && !m_vEntries.empty() )
|
||||
BANNERCACHE->CacheBanner( m_sBannerPath );
|
||||
|
||||
/* Cache each trail RadarValues that's slow to load, so we
|
||||
* don't have to do it at runtime. */
|
||||
if( !bUseCache )
|
||||
{
|
||||
CalculateRadarValues();
|
||||
|
||||
/* If we have any cache data, write the cache file. */
|
||||
if( m_RadarCache.size() )
|
||||
{
|
||||
CString sCachePath = GetCacheFilePath();
|
||||
Save( sCachePath, true );
|
||||
|
||||
SONGINDEX->AddCacheIndex( m_sPath, GetHashForFile(m_sPath) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Course::RevertFromDisk()
|
||||
{
|
||||
// trying to catch invalid an Course
|
||||
ASSERT( !m_sPath.empty() );
|
||||
|
||||
LoadFromCRSFile( m_sPath );
|
||||
CourseLoaderCRS::LoadFromCRSFile( m_sPath, *this );
|
||||
}
|
||||
|
||||
CString Course::GetCacheFilePath() const
|
||||
@@ -454,139 +160,6 @@ void Course::Init()
|
||||
m_iTrailCacheSeed = 0;
|
||||
}
|
||||
|
||||
void Course::Save( CString sPath, bool bSavingCache )
|
||||
{
|
||||
ASSERT( !m_bIsAutogen );
|
||||
|
||||
/* By default, save to the file we loaded from. */
|
||||
if( sPath == "" )
|
||||
sPath = m_sPath;
|
||||
|
||||
RageFile f;
|
||||
if( !f.Open( sPath, RageFile::WRITE ) )
|
||||
{
|
||||
LOG->Warn( "Could not write course file '%s': %s", sPath.c_str(), f.GetError().c_str() );
|
||||
return;
|
||||
}
|
||||
|
||||
f.PutLine( ssprintf("#COURSE:%s;", m_sMainTitle.c_str()) );
|
||||
if( m_sMainTitleTranslit != "" )
|
||||
f.PutLine( ssprintf("#COURSETRANSLIT:%s;", m_sMainTitleTranslit.c_str()) );
|
||||
if( m_bRepeat )
|
||||
f.PutLine( "#REPEAT:YES;" );
|
||||
if( m_iLives != -1 )
|
||||
f.PutLine( ssprintf("#LIVES:%i;", m_iLives) );
|
||||
|
||||
FOREACH_CourseDifficulty( cd )
|
||||
{
|
||||
if( m_iCustomMeter[cd] == -1 )
|
||||
continue;
|
||||
f.PutLine( ssprintf("#METER:%s:%i;", CourseDifficultyToString(cd).c_str(), m_iCustomMeter[cd]) );
|
||||
}
|
||||
|
||||
if( bSavingCache )
|
||||
{
|
||||
f.PutLine( "// cache tags:" );
|
||||
|
||||
RadarCache_t::const_iterator it;
|
||||
for( it = m_RadarCache.begin(); it != m_RadarCache.end(); ++it )
|
||||
{
|
||||
// #RADAR:type:difficulty:value,value,value...;
|
||||
const CacheEntry &entry = it->first;
|
||||
StepsType st = entry.first;
|
||||
CourseDifficulty cd = entry.second;
|
||||
|
||||
CStringArray asRadarValues;
|
||||
const RadarValues &rv = it->second;
|
||||
for( int r=0; r < NUM_RADAR_CATEGORIES; r++ )
|
||||
asRadarValues.push_back( ssprintf("%.3f", rv[r]) );
|
||||
CString sLine = ssprintf( "#RADAR:%i:%i:", st, cd );
|
||||
sLine += join( ",", asRadarValues ) + ";";
|
||||
f.PutLine( sLine );
|
||||
}
|
||||
f.PutLine( "// end cache tags" );
|
||||
}
|
||||
|
||||
for( unsigned i=0; i<m_vEntries.size(); i++ )
|
||||
{
|
||||
const CourseEntry& entry = m_vEntries[i];
|
||||
|
||||
for( unsigned j = 0; j < entry.attacks.size(); ++j )
|
||||
{
|
||||
if( j == 0 )
|
||||
f.PutLine( "#MODS:" );
|
||||
|
||||
const Attack &a = entry.attacks[j];
|
||||
f.Write( ssprintf( " TIME=%.2f:LEN=%.2f:MODS=%s",
|
||||
a.fStartSecond, a.fSecsRemaining, a.sModifiers.c_str() ) );
|
||||
|
||||
if( j+1 < entry.attacks.size() )
|
||||
f.Write( ":" );
|
||||
else
|
||||
f.Write( ";" );
|
||||
f.PutLine( "" );
|
||||
}
|
||||
|
||||
if( entry.fGainSeconds > 0 )
|
||||
f.PutLine( ssprintf("#GAINSECONDS:%f;", entry.fGainSeconds) );
|
||||
|
||||
if( entry.songSort == SongSort_MostPlays && entry.iChooseIndex != -1 )
|
||||
{
|
||||
f.Write( ssprintf( "#SONG:BEST%d", entry.iChooseIndex+1 ) );
|
||||
}
|
||||
else if( entry.songSort == SongSort_FewestPlays && entry.iChooseIndex != -1 )
|
||||
{
|
||||
f.Write( ssprintf( "#SONG:WORST%d", entry.iChooseIndex+1 ) );
|
||||
}
|
||||
else if( entry.pSong )
|
||||
{
|
||||
// strip off everything but the group name and song dir
|
||||
CStringArray as;
|
||||
ASSERT( entry.pSong != NULL );
|
||||
split( entry.pSong->GetSongDir(), "/", as );
|
||||
ASSERT( as.size() >= 2 );
|
||||
CString sGroup = as[ as.size()-2 ];
|
||||
CString sSong = as[ as.size()-1 ];
|
||||
f.Write( "#SONG:" + sGroup + '/' + sSong );
|
||||
}
|
||||
else if( !entry.sSongGroup.empty() )
|
||||
{
|
||||
f.Write( ssprintf( "#SONG:%s/*", entry.sSongGroup.c_str() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
f.Write( "#SONG:*" );
|
||||
}
|
||||
|
||||
f.Write( ":" );
|
||||
if( entry.baseDifficulty != DIFFICULTY_INVALID )
|
||||
f.Write( DifficultyToString(entry.baseDifficulty) );
|
||||
else if( entry.iLowMeter != -1 && entry.iHighMeter != -1 )
|
||||
f.Write( ssprintf( "%d..%d", entry.iLowMeter, entry.iHighMeter ) );
|
||||
f.Write( ":" );
|
||||
|
||||
CString sModifiers = entry.sModifiers;
|
||||
bool bDefaultSecret = entry.IsRandomSong();
|
||||
if( bDefaultSecret != entry.bSecret )
|
||||
{
|
||||
if( sModifiers != "" )
|
||||
sModifiers += ",";
|
||||
sModifiers += entry.bSecret? "noshowcourse":"showcourse";
|
||||
}
|
||||
|
||||
if( entry.bNoDifficult )
|
||||
{
|
||||
if( sModifiers != "" )
|
||||
sModifiers += ",";
|
||||
sModifiers += "nodifficult";
|
||||
}
|
||||
f.Write( sModifiers );
|
||||
|
||||
f.PutLine( ";" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Course::AutogenEndlessFromGroup( CString sGroupName, Difficulty diff )
|
||||
{
|
||||
m_bIsAutogen = true;
|
||||
|
||||
@@ -108,11 +108,10 @@ public:
|
||||
|
||||
bool m_bIsAutogen; // was this created by AutoGen?
|
||||
CString m_sPath;
|
||||
private:
|
||||
|
||||
CString m_sMainTitle, m_sMainTitleTranslit;
|
||||
CString m_sSubTitle, m_sSubTitleTranslit;
|
||||
|
||||
public:
|
||||
bool HasBanner() const;
|
||||
|
||||
CString m_sBannerPath;
|
||||
@@ -164,10 +163,8 @@ public:
|
||||
|
||||
bool ShowInDemonstrationAndRanking() const;
|
||||
|
||||
void LoadFromCRSFile( CString sPath );
|
||||
void RevertFromDisk();
|
||||
void Init();
|
||||
void Save( CString sPath = "", bool bSavingCache=false ); /* default is source file */
|
||||
void AutogenEndlessFromGroup( CString sGroupName, Difficulty dc );
|
||||
void AutogenNonstopFromGroup( CString sGroupName, Difficulty dc );
|
||||
void AutogenOniFromArtist( CString sArtistName, CString sArtistNameTranslit, vector<Song*> aSongs, Difficulty dc );
|
||||
@@ -196,7 +193,6 @@ public:
|
||||
// Lua
|
||||
void PushSelf( lua_State *L );
|
||||
|
||||
private:
|
||||
void CalculateRadarValues();
|
||||
|
||||
bool GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const;
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
#include "global.h"
|
||||
#include "CourseLoaderCRS.h"
|
||||
#include "RageLog.h"
|
||||
#include "Course.h"
|
||||
#include "RageUtil.h"
|
||||
#include "SongCacheIndex.h"
|
||||
#include "PrefsManager.h"
|
||||
#include "MsdFile.h"
|
||||
#include "PlayerOptions.h"
|
||||
#include "SongManager.h"
|
||||
#include "TitleSubstitution.h"
|
||||
#include "BannerCache.h"
|
||||
#include "RageFileManager.h"
|
||||
#include "Profile.h"
|
||||
#include "CourseWriterCRS.h"
|
||||
|
||||
const int MAX_EDIT_COURSE_SIZE_BYTES = 30*1024; // 30KB
|
||||
|
||||
bool CourseLoaderCRS::LoadFromCRSFile( const CString &_sPath, Course &out )
|
||||
{
|
||||
CString sPath = _sPath;
|
||||
|
||||
out.Init();
|
||||
|
||||
out.m_sPath = sPath; // save path
|
||||
|
||||
// save group name
|
||||
{
|
||||
CStringArray parts;
|
||||
split( sPath, "/", parts, false );
|
||||
if( parts.size() >= 4 ) // e.g. "/Courses/blah/fun.cvs"
|
||||
out.m_sGroupName = parts[parts.size()-2];
|
||||
}
|
||||
|
||||
|
||||
bool bUseCache = true;
|
||||
{
|
||||
/* First look in the cache for this course. Don't bother
|
||||
* honoring FastLoad for checking the course hash, since
|
||||
* courses are normally grouped into a few directories, not
|
||||
* one directory per course. XXX: if !FastLoad, regen
|
||||
* cache if the used songs have changed */
|
||||
unsigned uHash = SONGINDEX->GetCacheHash( out.m_sPath );
|
||||
if( !DoesFileExist(out.GetCacheFilePath()) )
|
||||
bUseCache = false;
|
||||
if( !PREFSMAN->m_bFastLoad && GetHashForDirectory(out.m_sPath) != uHash )
|
||||
bUseCache = false; // this cache is out of date
|
||||
}
|
||||
|
||||
if( bUseCache )
|
||||
{
|
||||
CString sCacheFile = out.GetCacheFilePath();
|
||||
LOG->Trace( "CourseLoaderCRS::LoadFromCRSFile(\"%s\") (\"%s\")", sPath.c_str(), sCacheFile.c_str() );
|
||||
sPath = sCacheFile.c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Trace( "CourseLoaderCRS::LoadFromCRSFile(\"%s\")", sPath.c_str() );
|
||||
}
|
||||
|
||||
MsdFile msd;
|
||||
if( !msd.ReadFile(sPath) )
|
||||
RageException::Throw( "Error opening CRS file '%s'.", sPath.c_str() );
|
||||
|
||||
const CString sFName = SetExtension( out.m_sPath, "" );
|
||||
|
||||
CStringArray arrayPossibleBanners;
|
||||
GetDirListing( sFName + "*.png", arrayPossibleBanners, false, true );
|
||||
GetDirListing( sFName + "*.jpg", arrayPossibleBanners, false, true );
|
||||
GetDirListing( sFName + "*.bmp", arrayPossibleBanners, false, true );
|
||||
GetDirListing( sFName + "*.gif", arrayPossibleBanners, false, true );
|
||||
if( !arrayPossibleBanners.empty() )
|
||||
out.m_sBannerPath = arrayPossibleBanners[0];
|
||||
|
||||
AttackArray attacks;
|
||||
float fGainSeconds = 0;
|
||||
for( unsigned i=0; i<msd.GetNumValues(); i++ )
|
||||
{
|
||||
CString sValueName = msd.GetParam(i, 0);
|
||||
const MsdFile::value_t &sParams = msd.GetValue(i);
|
||||
|
||||
// handle the data
|
||||
if( 0 == stricmp(sValueName, "COURSE") )
|
||||
out.m_sMainTitle = sParams[1];
|
||||
else if( 0 == stricmp(sValueName, "COURSETRANSLIT") )
|
||||
out.m_sMainTitleTranslit = sParams[1];
|
||||
else if( 0 == stricmp(sValueName, "REPEAT") )
|
||||
{
|
||||
CString str = sParams[1];
|
||||
str.MakeLower();
|
||||
if( str.Find("yes") != -1 )
|
||||
out.m_bRepeat = true;
|
||||
}
|
||||
|
||||
else if( 0 == stricmp(sValueName, "LIVES") )
|
||||
out.m_iLives = atoi( sParams[1] );
|
||||
|
||||
else if( 0 == stricmp(sValueName, "GAINSECONDS") )
|
||||
fGainSeconds = strtof( sParams[1], NULL );
|
||||
|
||||
else if( 0 == stricmp(sValueName, "METER") )
|
||||
{
|
||||
if( sParams.params.size() == 2 )
|
||||
{
|
||||
out.m_iCustomMeter[DIFFICULTY_MEDIUM] = atoi( sParams[1] ); /* compat */
|
||||
}
|
||||
else if( sParams.params.size() == 3 )
|
||||
{
|
||||
const CourseDifficulty cd = StringToCourseDifficulty( sParams[1] );
|
||||
if( cd == DIFFICULTY_INVALID )
|
||||
{
|
||||
LOG->Warn( "Course file '%s' contains an invalid #METER string: \"%s\"",
|
||||
sPath.c_str(), sParams[1].c_str() );
|
||||
continue;
|
||||
}
|
||||
out.m_iCustomMeter[cd] = atoi( sParams[2] );
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0 == stricmp(sValueName, "MODS") )
|
||||
{
|
||||
Attack attack;
|
||||
float end = -9999;
|
||||
for( unsigned j = 1; j < sParams.params.size(); ++j )
|
||||
{
|
||||
CStringArray sBits;
|
||||
split( sParams[j], "=", sBits, false );
|
||||
if( sBits.size() < 2 )
|
||||
continue;
|
||||
|
||||
TrimLeft( sBits[0] );
|
||||
TrimRight( sBits[0] );
|
||||
if( !sBits[0].CompareNoCase("TIME") )
|
||||
attack.fStartSecond = strtof( sBits[1], NULL );
|
||||
else if( !sBits[0].CompareNoCase("LEN") )
|
||||
attack.fSecsRemaining = strtof( sBits[1], NULL );
|
||||
else if( !sBits[0].CompareNoCase("END") )
|
||||
end = strtof( sBits[1], NULL );
|
||||
else if( !sBits[0].CompareNoCase("MODS") )
|
||||
{
|
||||
attack.sModifiers = sBits[1];
|
||||
if( end != -9999 )
|
||||
{
|
||||
ASSERT_M( end >= attack.fStartSecond, ssprintf("Attack ends before it starts. end %f, start %f", end, attack.fStartSecond) );
|
||||
attack.fSecsRemaining = end - attack.fStartSecond;
|
||||
end = -9999;
|
||||
}
|
||||
|
||||
// warn on invalid so we catch bogus mods on load
|
||||
PlayerOptions po;
|
||||
po.FromString( attack.sModifiers, true );
|
||||
|
||||
attacks.push_back( attack );
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Warn( "Unexpected value named '%s'", sBits[0].c_str() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if( 0 == stricmp(sValueName, "SONG") )
|
||||
{
|
||||
CourseEntry new_entry;
|
||||
|
||||
// infer entry::Type from the first param
|
||||
if( sParams[1].Left(strlen("BEST")) == "BEST" )
|
||||
{
|
||||
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1;
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_MostPlays;
|
||||
}
|
||||
else if( sParams[1].Left(strlen("WORST")) == "WORST" )
|
||||
{
|
||||
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1;
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_FewestPlays;
|
||||
}
|
||||
else if( sParams[1] == "*" )
|
||||
{
|
||||
new_entry.bSecret = true;
|
||||
}
|
||||
else if( sParams[1].Right(1) == "*" )
|
||||
{
|
||||
new_entry.bSecret = true;
|
||||
CString sSong = sParams[1];
|
||||
sSong.Replace( "\\", "/" );
|
||||
CStringArray bits;
|
||||
split( sSong, "/", bits );
|
||||
if( bits.size() == 2 )
|
||||
{
|
||||
new_entry.sSongGroup = bits[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Warn( "Course file '%s' contains a random_within_group entry '%s' that is invalid. "
|
||||
"Song should be in the format '<group>/*'.",
|
||||
sPath.c_str(), sSong.c_str());
|
||||
}
|
||||
|
||||
if( !SONGMAN->DoesSongGroupExist(new_entry.sSongGroup) )
|
||||
{
|
||||
/* XXX: We need a place to put "user warnings". This is too loud for info.txt--
|
||||
* it obscures important warnings--and regular users never look there, anyway. */
|
||||
LOG->Trace( "Course file '%s' random_within_group entry '%s' specifies a group that doesn't exist. "
|
||||
"This entry will be ignored.",
|
||||
sPath.c_str(), sSong.c_str());
|
||||
continue; // skip this #SONG
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CString sSong = sParams[1];
|
||||
new_entry.pSong = SONGMAN->FindSong( sSong );
|
||||
|
||||
if( new_entry.pSong == NULL )
|
||||
{
|
||||
/* XXX: We need a place to put "user warnings". This is too loud for info.txt--
|
||||
* it obscures important warnings--and regular users never look there, anyway. */
|
||||
LOG->Trace( "Course file '%s' contains a fixed song entry '%s' that does not exist. "
|
||||
"This entry will be ignored.",
|
||||
sPath.c_str(), sSong.c_str());
|
||||
continue; // skip this #SONG
|
||||
}
|
||||
}
|
||||
|
||||
new_entry.baseDifficulty = StringToDifficulty( sParams[2] );
|
||||
if( new_entry.baseDifficulty == DIFFICULTY_INVALID )
|
||||
{
|
||||
int retval = sscanf( sParams[2], "%d..%d", &new_entry.iLowMeter, &new_entry.iHighMeter );
|
||||
if( retval == 1 )
|
||||
new_entry.iHighMeter = new_entry.iLowMeter;
|
||||
else if( retval != 2 )
|
||||
{
|
||||
LOG->Warn("Course file '%s' contains an invalid difficulty setting: \"%s\", 3..6 used instead",
|
||||
sPath.c_str(), sParams[2].c_str());
|
||||
new_entry.iLowMeter = 3;
|
||||
new_entry.iHighMeter = 6;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
/* If "showcourse" or "noshowcourse" is in the list, force new_entry.secret
|
||||
* on or off. */
|
||||
CStringArray mods;
|
||||
split( sParams[3], ",", mods, true );
|
||||
for( int j = (int) mods.size()-1; j >= 0 ; --j )
|
||||
{
|
||||
CString &sMod = mods[j];
|
||||
TrimLeft( sMod );
|
||||
TrimRight( sMod );
|
||||
if( !sMod.CompareNoCase("showcourse") )
|
||||
new_entry.bSecret = false;
|
||||
else if( !sMod.CompareNoCase("noshowcourse") )
|
||||
new_entry.bSecret = true;
|
||||
else if( !sMod.CompareNoCase("nodifficult") )
|
||||
new_entry.bNoDifficult = true;
|
||||
else
|
||||
continue;
|
||||
mods.erase(mods.begin() + j);
|
||||
}
|
||||
new_entry.sModifiers = join( ",", mods );
|
||||
}
|
||||
|
||||
new_entry.attacks = attacks;
|
||||
new_entry.fGainSeconds = fGainSeconds;
|
||||
attacks.clear();
|
||||
|
||||
out.m_vEntries.push_back( new_entry );
|
||||
}
|
||||
else if( bUseCache && !stricmp(sValueName, "RADAR") )
|
||||
{
|
||||
StepsType st = (StepsType) atoi(sParams[1]);
|
||||
CourseDifficulty cd = (CourseDifficulty) atoi(sParams[2]);
|
||||
|
||||
RadarValues rv;
|
||||
rv.FromString( sParams[3] );
|
||||
out.m_RadarCache[Course::CacheEntry(st, cd)] = rv;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Warn( "Unexpected value named '%s'", sValueName.c_str() );
|
||||
}
|
||||
}
|
||||
static TitleSubst tsub("courses");
|
||||
|
||||
TitleFields title;
|
||||
title.Title = out.m_sMainTitle;
|
||||
title.TitleTranslit = out.m_sMainTitleTranslit;
|
||||
tsub.Subst( title );
|
||||
out.m_sMainTitle = title.Title;
|
||||
out.m_sMainTitleTranslit = title.TitleTranslit;
|
||||
|
||||
/* Cache and load the course banner. Only bother doing this if at least one
|
||||
* song was found in the course. */
|
||||
if( out.m_sBannerPath != "" && !out.m_vEntries.empty() )
|
||||
BANNERCACHE->CacheBanner( out.m_sBannerPath );
|
||||
|
||||
/* Cache each trail RadarValues that's slow to load, so we
|
||||
* don't have to do it at runtime. */
|
||||
if( !bUseCache )
|
||||
{
|
||||
out.CalculateRadarValues();
|
||||
|
||||
/* If we have any cache data, write the cache file. */
|
||||
if( out.m_RadarCache.size() )
|
||||
{
|
||||
CString sCachePath = out.GetCacheFilePath();
|
||||
CourseWriterCRS::Write( out, sCachePath, true );
|
||||
|
||||
SONGINDEX->AddCacheIndex( out.m_sPath, GetHashForFile(out.m_sPath) );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CourseLoaderCRS::LoadEdit( const CString &sEditFilePath, ProfileSlot slot )
|
||||
{
|
||||
LOG->Trace( "CourseLoaderCRS::LoadEdit(%s)", sEditFilePath.c_str() );
|
||||
|
||||
int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath );
|
||||
if( iBytes > MAX_EDIT_COURSE_SIZE_BYTES )
|
||||
{
|
||||
LOG->Warn( "The edit '%s' is unreasonably large. It won't be loaded.", sEditFilePath.c_str() );
|
||||
return false;
|
||||
}
|
||||
|
||||
Course *pCourse = new Course;
|
||||
LoadFromCRSFile( sEditFilePath, *pCourse );
|
||||
MsdFile msd;
|
||||
if( !msd.ReadFile( sEditFilePath ) )
|
||||
{
|
||||
LOG->Warn( "Error opening edit file \"%s\": %s", sEditFilePath.c_str(), msd.GetError().c_str() );
|
||||
SAFE_DELETE( pCourse );
|
||||
return false;
|
||||
}
|
||||
|
||||
pCourse->m_LoadedFromProfile = slot;
|
||||
|
||||
SONGMAN->AddCourse( pCourse );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (c) 2001-2004 Chris Danford, Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,41 @@
|
||||
/* CourseLoaderCRS - Reads a Course from an .CRS file. */
|
||||
|
||||
#ifndef CourseLoaderCRS_H
|
||||
#define CourseLoaderCRS_H
|
||||
|
||||
#include "GameConstantsAndTypes.h"
|
||||
class Course;
|
||||
|
||||
class CourseLoaderCRS
|
||||
{
|
||||
public:
|
||||
static bool LoadFromCRSFile( const CString &sPath, Course &out );
|
||||
static bool LoadEdit( const CString &sEditFilePath, ProfileSlot slot );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2001-2004 Chris Danford
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,169 @@
|
||||
#include "global.h"
|
||||
#include "CourseWriterCRS.h"
|
||||
#include "Course.h"
|
||||
#include "RageFile.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageUtil.h"
|
||||
#include "Song.h"
|
||||
|
||||
|
||||
bool CourseWriterCRS::Write( const Course &course, const CString &sPath, bool bSavingCache )
|
||||
{
|
||||
ASSERT( !course.m_bIsAutogen );
|
||||
|
||||
RageFile f;
|
||||
if( !f.Open( sPath, RageFile::WRITE ) )
|
||||
{
|
||||
LOG->Warn( "Could not write course file '%s': %s", sPath.c_str(), f.GetError().c_str() );
|
||||
return false;
|
||||
}
|
||||
|
||||
f.PutLine( ssprintf("#COURSE:%s;", course.m_sMainTitle.c_str()) );
|
||||
if( course.m_sMainTitleTranslit != "" )
|
||||
f.PutLine( ssprintf("#COURSETRANSLIT:%s;", course.m_sMainTitleTranslit.c_str()) );
|
||||
if( course.m_bRepeat )
|
||||
f.PutLine( "#REPEAT:YES;" );
|
||||
if( course.m_iLives != -1 )
|
||||
f.PutLine( ssprintf("#LIVES:%i;", course.m_iLives) );
|
||||
|
||||
FOREACH_CourseDifficulty( cd )
|
||||
{
|
||||
if( course.m_iCustomMeter[cd] == -1 )
|
||||
continue;
|
||||
f.PutLine( ssprintf("#METER:%s:%i;", CourseDifficultyToString(cd).c_str(), course.m_iCustomMeter[cd]) );
|
||||
}
|
||||
|
||||
if( bSavingCache )
|
||||
{
|
||||
f.PutLine( "// cache tags:" );
|
||||
|
||||
Course::RadarCache_t::const_iterator it;
|
||||
for( it = course.m_RadarCache.begin(); it != course.m_RadarCache.end(); ++it )
|
||||
{
|
||||
// #RADAR:type:difficulty:value,value,value...;
|
||||
const Course::CacheEntry &entry = it->first;
|
||||
StepsType st = entry.first;
|
||||
CourseDifficulty cd = entry.second;
|
||||
|
||||
CStringArray asRadarValues;
|
||||
const RadarValues &rv = it->second;
|
||||
for( int r=0; r < NUM_RADAR_CATEGORIES; r++ )
|
||||
asRadarValues.push_back( ssprintf("%.3f", rv[r]) );
|
||||
CString sLine = ssprintf( "#RADAR:%i:%i:", st, cd );
|
||||
sLine += join( ",", asRadarValues ) + ";";
|
||||
f.PutLine( sLine );
|
||||
}
|
||||
f.PutLine( "// end cache tags" );
|
||||
}
|
||||
|
||||
for( unsigned i=0; i<course.m_vEntries.size(); i++ )
|
||||
{
|
||||
const CourseEntry& entry = course.m_vEntries[i];
|
||||
|
||||
for( unsigned j = 0; j < entry.attacks.size(); ++j )
|
||||
{
|
||||
if( j == 0 )
|
||||
f.PutLine( "#MODS:" );
|
||||
|
||||
const Attack &a = entry.attacks[j];
|
||||
f.Write( ssprintf( " TIME=%.2f:LEN=%.2f:MODS=%s",
|
||||
a.fStartSecond, a.fSecsRemaining, a.sModifiers.c_str() ) );
|
||||
|
||||
if( j+1 < entry.attacks.size() )
|
||||
f.Write( ":" );
|
||||
else
|
||||
f.Write( ";" );
|
||||
f.PutLine( "" );
|
||||
}
|
||||
|
||||
if( entry.fGainSeconds > 0 )
|
||||
f.PutLine( ssprintf("#GAINSECONDS:%f;", entry.fGainSeconds) );
|
||||
|
||||
if( entry.songSort == SongSort_MostPlays && entry.iChooseIndex != -1 )
|
||||
{
|
||||
f.Write( ssprintf( "#SONG:BEST%d", entry.iChooseIndex+1 ) );
|
||||
}
|
||||
else if( entry.songSort == SongSort_FewestPlays && entry.iChooseIndex != -1 )
|
||||
{
|
||||
f.Write( ssprintf( "#SONG:WORST%d", entry.iChooseIndex+1 ) );
|
||||
}
|
||||
else if( entry.pSong )
|
||||
{
|
||||
// strip off everything but the group name and song dir
|
||||
CStringArray as;
|
||||
ASSERT( entry.pSong != NULL );
|
||||
split( entry.pSong->GetSongDir(), "/", as );
|
||||
ASSERT( as.size() >= 2 );
|
||||
CString sGroup = as[ as.size()-2 ];
|
||||
CString sSong = as[ as.size()-1 ];
|
||||
f.Write( "#SONG:" + sGroup + '/' + sSong );
|
||||
}
|
||||
else if( !entry.sSongGroup.empty() )
|
||||
{
|
||||
f.Write( ssprintf( "#SONG:%s/*", entry.sSongGroup.c_str() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
f.Write( "#SONG:*" );
|
||||
}
|
||||
|
||||
f.Write( ":" );
|
||||
if( entry.baseDifficulty != DIFFICULTY_INVALID )
|
||||
f.Write( DifficultyToString(entry.baseDifficulty) );
|
||||
else if( entry.iLowMeter != -1 && entry.iHighMeter != -1 )
|
||||
f.Write( ssprintf( "%d..%d", entry.iLowMeter, entry.iHighMeter ) );
|
||||
f.Write( ":" );
|
||||
|
||||
CString sModifiers = entry.sModifiers;
|
||||
bool bDefaultSecret = entry.IsRandomSong();
|
||||
if( bDefaultSecret != entry.bSecret )
|
||||
{
|
||||
if( sModifiers != "" )
|
||||
sModifiers += ",";
|
||||
sModifiers += entry.bSecret? "noshowcourse":"showcourse";
|
||||
}
|
||||
|
||||
if( entry.bNoDifficult )
|
||||
{
|
||||
if( sModifiers != "" )
|
||||
sModifiers += ",";
|
||||
sModifiers += "nodifficult";
|
||||
}
|
||||
f.Write( sModifiers );
|
||||
|
||||
f.PutLine( ";" );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CourseWriterCRS::GetEditFile( const Course *pCourse, CString &sOut )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (c) 2001-2004 Chris Danford, Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,40 @@
|
||||
/* CourseWriterCRS - Writes a Course to an .CRS file. */
|
||||
|
||||
#ifndef CourseWriterCRS_H
|
||||
#define CourseWriterCRS_H
|
||||
|
||||
class Course;
|
||||
|
||||
class CourseWriterCRS
|
||||
{
|
||||
public:
|
||||
static bool Write( const Course &course, const CString &sPath, bool bSavingCache );
|
||||
static void GetEditFile( const Course *pCourse, CString &sOut );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2001-2005 Chris Danford
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -820,7 +820,8 @@ void GameCommand::ApplySelf( const vector<PlayerNumber> &vpns ) const
|
||||
int iNumSuccessful = 0;
|
||||
|
||||
vector<CString> vsEditFiles;
|
||||
GetDirListing( PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE)+EDIT_SUBDIR+"*.edit", vsEditFiles, false, true );
|
||||
GetDirListing( PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE)+EDIT_STEPS_SUBDIR+"*.edit", vsEditFiles, false, true );
|
||||
GetDirListing( PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE)+EDIT_COURSES_SUBDIR+"*.crs", vsEditFiles, false, true );
|
||||
FOREACH_CONST( CString, vsEditFiles, i )
|
||||
{
|
||||
iNumAttempted++;
|
||||
@@ -966,23 +967,42 @@ void GameCommand::ApplySelf( const vector<PlayerNumber> &vpns ) const
|
||||
|
||||
bTriedToCopy = true;
|
||||
|
||||
CString sFromDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_SUBDIR;
|
||||
CString sToDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDITS_SUBDIR;
|
||||
|
||||
int iNumAttempted = 0;
|
||||
int iNumSuccessful = 0;
|
||||
int iNumOverwritten = 0;
|
||||
|
||||
vector<CString> vsEditFiles;
|
||||
GetDirListing( sFromDir+"*.edit", vsEditFiles, false, false );
|
||||
FOREACH_CONST( CString, vsEditFiles, i )
|
||||
{
|
||||
iNumAttempted++;
|
||||
if( DoesFileExist(sToDir+*i) )
|
||||
iNumOverwritten++;
|
||||
bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i );
|
||||
if( bSuccess )
|
||||
iNumSuccessful++;
|
||||
CString sFromDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_STEPS_SUBDIR;
|
||||
CString sToDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDIT_STEPS_SUBDIR;
|
||||
|
||||
vector<CString> vsFiles;
|
||||
GetDirListing( sFromDir+"*.edit", vsFiles, false, false );
|
||||
FOREACH_CONST( CString, vsFiles, i )
|
||||
{
|
||||
iNumAttempted++;
|
||||
if( DoesFileExist(sToDir+*i) )
|
||||
iNumOverwritten++;
|
||||
bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i );
|
||||
if( bSuccess )
|
||||
iNumSuccessful++;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
CString sFromDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_COURSES_SUBDIR;
|
||||
CString sToDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDIT_COURSES_SUBDIR;
|
||||
|
||||
vector<CString> vsFiles;
|
||||
GetDirListing( sFromDir+"*.crs", vsFiles, false, false );
|
||||
FOREACH_CONST( CString, vsFiles, i )
|
||||
{
|
||||
iNumAttempted++;
|
||||
if( DoesFileExist(sToDir+*i) )
|
||||
iNumOverwritten++;
|
||||
bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i );
|
||||
if( bSuccess )
|
||||
iNumSuccessful++;
|
||||
}
|
||||
}
|
||||
|
||||
MEMCARDMAN->UnmountCard(pn);
|
||||
@@ -1008,23 +1028,42 @@ void GameCommand::ApplySelf( const vector<PlayerNumber> &vpns ) const
|
||||
|
||||
bTriedToCopy = true;
|
||||
|
||||
CString sFromDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDITS_SUBDIR;
|
||||
CString sToDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_SUBDIR;
|
||||
|
||||
int iNumAttempted = 0;
|
||||
int iNumSuccessful = 0;
|
||||
int iNumOverwritten = 0;
|
||||
|
||||
vector<CString> vsEditFiles;
|
||||
GetDirListing( sFromDir+"*.edit", vsEditFiles, false, false );
|
||||
FOREACH_CONST( CString, vsEditFiles, i )
|
||||
{
|
||||
iNumAttempted++;
|
||||
if( DoesFileExist(sToDir+*i) )
|
||||
iNumOverwritten++;
|
||||
bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i );
|
||||
if( bSuccess )
|
||||
iNumSuccessful++;
|
||||
CString sFromDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDIT_STEPS_SUBDIR;
|
||||
CString sToDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_STEPS_SUBDIR;
|
||||
|
||||
vector<CString> vsFiles;
|
||||
GetDirListing( sFromDir+"*.edit", vsFiles, false, false );
|
||||
FOREACH_CONST( CString, vsFiles, i )
|
||||
{
|
||||
iNumAttempted++;
|
||||
if( DoesFileExist(sToDir+*i) )
|
||||
iNumOverwritten++;
|
||||
bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i );
|
||||
if( bSuccess )
|
||||
iNumSuccessful++;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
CString sFromDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDIT_COURSES_SUBDIR;
|
||||
CString sToDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_COURSES_SUBDIR;
|
||||
|
||||
vector<CString> vsFiles;
|
||||
GetDirListing( sFromDir+"*.crs", vsFiles, false, false );
|
||||
FOREACH_CONST( CString, vsFiles, i )
|
||||
{
|
||||
iNumAttempted++;
|
||||
if( DoesFileExist(sToDir+*i) )
|
||||
iNumOverwritten++;
|
||||
bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i );
|
||||
if( bSuccess )
|
||||
iNumSuccessful++;
|
||||
}
|
||||
}
|
||||
|
||||
MEMCARDMAN->UnmountCard(pn);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "song.h"
|
||||
#include "Steps.h"
|
||||
|
||||
#define MAX_EDIT_SIZE_BYTES 20*1024 // 20 KB
|
||||
const int MAX_EDIT_STEPS_SIZE_BYTES = 30*1024; // 30KB
|
||||
|
||||
void SMLoader::LoadFromSMTokens(
|
||||
CString sStepsType,
|
||||
@@ -464,10 +464,10 @@ bool SMLoader::LoadFromDir( CString sPath, Song &out )
|
||||
|
||||
bool SMLoader::LoadEdit( CString sEditFilePath, ProfileSlot slot )
|
||||
{
|
||||
LOG->Trace( "Song::LoadEdit(%s)", sEditFilePath.c_str() );
|
||||
LOG->Trace( "SMLoader::LoadEdit(%s)", sEditFilePath.c_str() );
|
||||
|
||||
int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath );
|
||||
if( iBytes > MAX_EDIT_SIZE_BYTES )
|
||||
if( iBytes > MAX_EDIT_STEPS_SIZE_BYTES )
|
||||
{
|
||||
LOG->Warn( "The edit '%s' is unreasonably large. It won't be loaded.", sEditFilePath.c_str() );
|
||||
return false;
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
#include "GameState.h"
|
||||
#include "Character.h"
|
||||
|
||||
//
|
||||
// Old file versions for backward compatibility
|
||||
//
|
||||
const CString STATS_XSL = "Stats.xsl";
|
||||
const CString COMMON_XSL = "Common.xsl";
|
||||
const CString STATS_XML = "Stats.xml";
|
||||
@@ -36,7 +33,8 @@ const CString EDITABLE_INI = "Editable.ini";
|
||||
const CString DONT_SHARE_SIG = "DontShare.sig";
|
||||
const CString PUBLIC_KEY_FILE = "public.key";
|
||||
const CString SCREENSHOTS_SUBDIR = "Screenshots/";
|
||||
const CString EDITS_SUBDIR = "Edits/";
|
||||
const CString EDIT_STEPS_SUBDIR = "Edits/";
|
||||
const CString EDIT_COURSES_SUBDIR = "EditCourses/";
|
||||
const CString LASTGOOD_SUBDIR = "LastGood/";
|
||||
|
||||
#define GUID_SIZE_BYTES 8
|
||||
@@ -834,7 +832,8 @@ bool Profile::SaveAllToDir( CString sDir, bool bSignData ) const
|
||||
SaveStatsWebPageToDir( sDir );
|
||||
|
||||
// Empty directories if none exist.
|
||||
FILEMAN->CreateDir( sDir + EDITS_SUBDIR );
|
||||
FILEMAN->CreateDir( sDir + EDIT_STEPS_SUBDIR );
|
||||
FILEMAN->CreateDir( sDir + EDIT_COURSES_SUBDIR );
|
||||
FILEMAN->CreateDir( sDir + SCREENSHOTS_SUBDIR );
|
||||
|
||||
return bSaved;
|
||||
|
||||
@@ -43,7 +43,8 @@ extern const CString DONT_SHARE_SIG;
|
||||
// The file contains a signature of the STATS_XML's signature.
|
||||
extern const CString PUBLIC_KEY_FILE;
|
||||
extern const CString SCREENSHOTS_SUBDIR;
|
||||
extern const CString EDITS_SUBDIR;
|
||||
extern const CString EDIT_STEPS_SUBDIR;
|
||||
extern const CString EDIT_COURSES_SUBDIR;
|
||||
extern const CString LASTGOOD_SUBDIR;
|
||||
|
||||
const unsigned int PROFILE_MAX_DISPLAY_NAME_LENGTH = 12;
|
||||
|
||||
@@ -386,31 +386,8 @@ bool ProfileManager::RenameLocalProfile( CString sProfileID, CString sNewName )
|
||||
|
||||
bool ProfileManager::DeleteLocalProfile( CString sProfileID )
|
||||
{
|
||||
// delete all files in profile dir
|
||||
CString sProfileDir = LocalProfileIdToDir( sProfileID );
|
||||
CStringArray asFilesToDelete;
|
||||
GetDirListing( sProfileDir + "*", asFilesToDelete, false, true );
|
||||
for( unsigned i=0; i<asFilesToDelete.size(); i++ )
|
||||
FILEMAN->Remove( asFilesToDelete[i] );
|
||||
|
||||
// delete edits
|
||||
GetDirListing( sProfileDir + EDITS_SUBDIR + "*", asFilesToDelete, false, true );
|
||||
for( unsigned i=0; i<asFilesToDelete.size(); i++ )
|
||||
FILEMAN->Remove( asFilesToDelete[i] );
|
||||
|
||||
// delete lastgood
|
||||
GetDirListing( sProfileDir + LASTGOOD_SUBDIR + "*", asFilesToDelete, false, true );
|
||||
for( unsigned i=0; i<asFilesToDelete.size(); i++ )
|
||||
FILEMAN->Remove( asFilesToDelete[i] );
|
||||
|
||||
// remove edits dir
|
||||
FILEMAN->Remove( sProfileDir + "/" + EDITS_SUBDIR );
|
||||
|
||||
// remove lastgood dir
|
||||
FILEMAN->Remove( sProfileDir + "/" + LASTGOOD_SUBDIR );
|
||||
|
||||
// remove profile dir
|
||||
bool bResult = FILEMAN->Remove( sProfileDir );
|
||||
bool bResult = DeleteRecursive( sProfileDir );
|
||||
|
||||
RefreshLocalProfilesFromDisk();
|
||||
return bResult;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "RageUtil_FileDB.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageThreads.h"
|
||||
#include "Foreach.h"
|
||||
|
||||
#include <cerrno>
|
||||
#if defined(LINUX)
|
||||
@@ -874,6 +875,23 @@ void GetDirListingRecursive( const CString &sDir, const CString &sMatch, CString
|
||||
}
|
||||
}
|
||||
|
||||
bool DeleteRecursive( const CString &sDir )
|
||||
{
|
||||
ASSERT( sDir.Right(1) == "/" );
|
||||
|
||||
vector<CString> vsFiles;
|
||||
GetDirListing( sDir+"*", vsFiles, false, true );
|
||||
FOREACH_CONST( CString, vsFiles, s )
|
||||
{
|
||||
if( IsADirectory(*s) )
|
||||
DeleteRecursive( *s );
|
||||
else
|
||||
FILEMAN->Remove( *s );
|
||||
}
|
||||
|
||||
return FILEMAN->Remove( sDir );
|
||||
}
|
||||
|
||||
void FlushDirCache()
|
||||
{
|
||||
FILEMAN->FlushDirCache( "" );
|
||||
|
||||
@@ -395,6 +395,7 @@ typedef basic_string<char,char_traits_char_nocase> istring;
|
||||
* declared here since they're used in many places. */
|
||||
void GetDirListing( const CString &sPath, CStringArray &AddTo, bool bOnlyDirs=false, bool bReturnPathToo=false );
|
||||
void GetDirListingRecursive( const CString &sDir, const CString &sMatch, CStringArray &AddTo ); /* returns path too */
|
||||
bool DeleteRecursive( const CString &sDir ); /* delete the dir and all files/subdirs inside it */
|
||||
bool DoesFileExist( const CString &sPath );
|
||||
bool IsAFile( const CString &sPath );
|
||||
bool IsADirectory( const CString &sPath );
|
||||
|
||||
@@ -3038,7 +3038,7 @@ void ScreenEdit::SetupCourseAttacks()
|
||||
|
||||
if( GAMESTATE->m_pCurCourse )
|
||||
{
|
||||
GAMESTATE->m_pCurCourse->LoadFromCRSFile( GAMESTATE->m_pCurCourse->m_sPath );
|
||||
GAMESTATE->m_pCurCourse->RevertFromDisk(); // Remove this and have a separate reload key?
|
||||
|
||||
AttackArray Attacks;
|
||||
for( unsigned e = 0; e < GAMESTATE->m_pCurCourse->m_vEntries.size(); ++e )
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include "StatsManager.h"
|
||||
#include "Style.h"
|
||||
#include "BackgroundUtil.h"
|
||||
#include "Profile.h"
|
||||
#include "CourseLoaderCRS.h"
|
||||
|
||||
SongManager* SONGMAN = NULL; // global and accessable from anywhere in our program
|
||||
|
||||
@@ -39,7 +41,6 @@ const CString SONGS_DIR = "Songs/";
|
||||
const CString COURSES_DIR = "Courses/";
|
||||
|
||||
const int MAX_EDITS_PER_PROFILE = 200;
|
||||
const int MAX_EDIT_SIZE_BYTES = 30*1024; // 30KB
|
||||
|
||||
static const ThemeMetric<RageColor> BEGINNER_COLOR ("SongManager","BeginnerColor");
|
||||
static const ThemeMetric<RageColor> EASY_COLOR ("SongManager","EasyColor");
|
||||
@@ -386,34 +387,39 @@ RageColor SongManager::GetSongColor( const Song* pSong )
|
||||
return GetSongGroupColor( pSong->m_sGroupName );
|
||||
}
|
||||
|
||||
CString SongManager::GetCourseGroupBannerPath( CString sCourseGroup )
|
||||
CString SongManager::GetCourseGroupBannerPath( const CString &sCourseGroup )
|
||||
{
|
||||
for( unsigned i = 0; i < m_sCourseGroupNames.size(); ++i )
|
||||
map<CString, CourseGroupInfo>::const_iterator iter = m_mapCourseGroupToInfo.find( sCourseGroup );
|
||||
if( iter == m_mapCourseGroupToInfo.end() )
|
||||
{
|
||||
if( sCourseGroup == m_sCourseGroupNames[i] )
|
||||
return m_sCourseGroupBannerPaths[i];
|
||||
ASSERT_M( 0, ssprintf("requested banner for course group '%s' that doesn't exist",sCourseGroup.c_str()) );
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return iter->second.m_sBannerPath;
|
||||
}
|
||||
|
||||
ASSERT_M( 0, ssprintf("requested banner for course group '%s' that doesn't exist",sCourseGroup.c_str()) );
|
||||
return "";
|
||||
}
|
||||
|
||||
void SongManager::GetCourseGroupNames( CStringArray &AddTo )
|
||||
{
|
||||
AddTo.insert(AddTo.end(), m_sCourseGroupNames.begin(), m_sCourseGroupNames.end() );
|
||||
FOREACHM_CONST( CString, CourseGroupInfo, m_mapCourseGroupToInfo, iter )
|
||||
AddTo.push_back( iter->first );
|
||||
}
|
||||
|
||||
bool SongManager::DoesCourseGroupExist( CString sCourseGroup )
|
||||
bool SongManager::DoesCourseGroupExist( const CString &sCourseGroup )
|
||||
{
|
||||
return find( m_sCourseGroupNames.begin(), m_sCourseGroupNames.end(), sCourseGroup ) != m_sCourseGroupNames.end();
|
||||
return m_mapCourseGroupToInfo.find( sCourseGroup ) != m_mapCourseGroupToInfo.end();
|
||||
}
|
||||
|
||||
RageColor SongManager::GetCourseGroupColor( const CString &sCourseGroup )
|
||||
{
|
||||
for( unsigned i=0; i<m_sCourseGroupNames.size(); i++ )
|
||||
int iIndex = 0;
|
||||
FOREACHM_CONST( CString, CourseGroupInfo, m_mapCourseGroupToInfo, iter )
|
||||
{
|
||||
if( m_sCourseGroupNames[i] == sCourseGroup )
|
||||
return SONG_GROUP_COLOR.GetValue( i%NUM_SONG_GROUP_COLORS );
|
||||
if( iter->first == sCourseGroup )
|
||||
return SONG_GROUP_COLOR.GetValue( iIndex%NUM_SONG_GROUP_COLORS );
|
||||
iIndex++;
|
||||
}
|
||||
|
||||
ASSERT_M( 0, ssprintf("requested color for course group '%s' that doesn't exist",sCourseGroup.c_str()) );
|
||||
@@ -476,7 +482,7 @@ int SongManager::GetNumCourses() const
|
||||
|
||||
int SongManager::GetNumCourseGroups() const
|
||||
{
|
||||
return m_sCourseGroupNames.size();
|
||||
return m_mapCourseGroupToInfo.size();
|
||||
}
|
||||
|
||||
CString SongManager::ShortenGroupName( CString sLongGroupName )
|
||||
@@ -524,7 +530,6 @@ void SongManager::InitCoursesFromDisk( LoadingWindow *ld )
|
||||
{
|
||||
LOG->Trace( "Loading courses." );
|
||||
|
||||
m_sCourseGroupNames.clear();
|
||||
|
||||
//
|
||||
// Load courses from in Courses dir
|
||||
@@ -535,7 +540,7 @@ void SongManager::InitCoursesFromDisk( LoadingWindow *ld )
|
||||
for( unsigned i=0; i<saCourseFiles.size(); i++ )
|
||||
{
|
||||
Course* pCourse = new Course;
|
||||
pCourse->LoadFromCRSFile( saCourseFiles[i] );
|
||||
CourseLoaderCRS::LoadFromCRSFile( saCourseFiles[i], *pCourse );
|
||||
m_pCourses.push_back( pCourse );
|
||||
|
||||
if( ld )
|
||||
@@ -545,24 +550,18 @@ void SongManager::InitCoursesFromDisk( LoadingWindow *ld )
|
||||
Basename(saCourseFiles[i]).c_str()));
|
||||
ld->Paint();
|
||||
}
|
||||
|
||||
}
|
||||
if( !saCourseFiles.empty() )
|
||||
m_sCourseGroupNames.push_back( "" );
|
||||
}
|
||||
|
||||
// TODO: Search for course group banners if any
|
||||
FOREACH( CString, m_sCourseGroupNames, s )
|
||||
m_sCourseGroupBannerPaths.push_back( "" );
|
||||
|
||||
|
||||
// Find all group directories in Courses dir
|
||||
{
|
||||
GetDirListing( COURSES_DIR+"*", m_sCourseGroupNames, true );
|
||||
StripCvs( m_sCourseGroupNames );
|
||||
SortCStringArray( m_sCourseGroupNames );
|
||||
vector<CString> vsCourseGroupNames;
|
||||
GetDirListing( COURSES_DIR+"*", vsCourseGroupNames, true );
|
||||
StripCvs( vsCourseGroupNames );
|
||||
SortCStringArray( vsCourseGroupNames );
|
||||
|
||||
FOREACH( CString, m_sCourseGroupNames, sCourseGroup ) // for each dir in /Courses/
|
||||
FOREACH( CString, vsCourseGroupNames, sCourseGroup ) // for each dir in /Courses/
|
||||
{
|
||||
// Find all CRS files in this group directory
|
||||
CStringArray vsCoursePaths;
|
||||
@@ -580,11 +579,13 @@ void SongManager::InitCoursesFromDisk( LoadingWindow *ld )
|
||||
}
|
||||
|
||||
Course* pCourse = new Course;
|
||||
pCourse->LoadFromCRSFile( *sCoursePath );
|
||||
CourseLoaderCRS::LoadFromCRSFile( *sCoursePath, *pCourse );
|
||||
m_pCourses.push_back( pCourse );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RefreshCourseGroupInfo();
|
||||
}
|
||||
|
||||
void SongManager::InitAutogenCourses()
|
||||
@@ -679,7 +680,7 @@ void SongManager::FreeCourses()
|
||||
m_pBestCourses[i][ct].clear();
|
||||
m_pShuffledCourses.clear();
|
||||
|
||||
m_sCourseGroupNames.clear();
|
||||
m_mapCourseGroupToInfo.clear();
|
||||
}
|
||||
|
||||
void SongManager::AddCourse( Course *pCourse )
|
||||
@@ -687,6 +688,7 @@ void SongManager::AddCourse( Course *pCourse )
|
||||
m_pCourses.push_back( pCourse );
|
||||
UpdateBest();
|
||||
UpdateShuffled();
|
||||
m_mapCourseGroupToInfo[ pCourse->m_sGroupName ]; // insert
|
||||
}
|
||||
|
||||
void SongManager::DeleteCourse( Course *pCourse )
|
||||
@@ -696,6 +698,7 @@ void SongManager::DeleteCourse( Course *pCourse )
|
||||
m_pCourses.erase( iter );
|
||||
UpdateBest();
|
||||
UpdateShuffled();
|
||||
RefreshCourseGroupInfo();
|
||||
}
|
||||
|
||||
/* Called periodically to wipe out cached NoteData. This is called when we change
|
||||
@@ -927,7 +930,7 @@ bool SongManager::GetExtraStageInfoFromCourse( bool bExtra2, CString sPreferredG
|
||||
return false;
|
||||
|
||||
Course course;
|
||||
course.LoadFromCRSFile( sCoursePath );
|
||||
CourseLoaderCRS::LoadFromCRSFile( sCoursePath, course );
|
||||
if( course.GetEstimatedNumStages() <= 0 ) return false;
|
||||
|
||||
Trail *pTrail = course.GetTrail( GAMESTATE->GetCurrentStyle()->m_StepsType );
|
||||
@@ -1259,31 +1262,61 @@ void SongManager::UpdateRankingCourses()
|
||||
}
|
||||
}
|
||||
|
||||
void SongManager::RefreshCourseGroupInfo()
|
||||
{
|
||||
m_mapCourseGroupToInfo.clear();
|
||||
|
||||
FOREACH_CONST( Course*, m_pCourses, c )
|
||||
{
|
||||
m_mapCourseGroupToInfo[(*c)->m_sGroupName]; // insert
|
||||
}
|
||||
|
||||
// TODO: Search for course group banners
|
||||
FOREACHM( CString, CourseGroupInfo, m_mapCourseGroupToInfo, iter )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void SongManager::LoadAllFromProfileDir( const CString &sProfileDir, ProfileSlot slot )
|
||||
{
|
||||
//
|
||||
// Load all .edit files.
|
||||
//
|
||||
CString sEditsDir = sProfileDir + EDIT_SUBDIR;
|
||||
|
||||
CStringArray asEditsFilesWithPath;
|
||||
GetDirListing( sEditsDir+"*.edit", asEditsFilesWithPath, false, true );
|
||||
|
||||
int iNumEditsLoaded = GetNumEditsLoadedFromProfile( slot );
|
||||
int size = min( (int) asEditsFilesWithPath.size(), MAX_EDITS_PER_PROFILE - iNumEditsLoaded );
|
||||
|
||||
for( int i=0; i<size; i++ )
|
||||
{
|
||||
CString fn = asEditsFilesWithPath[i];
|
||||
//
|
||||
// Load all edit steps
|
||||
//
|
||||
CString sDir = sProfileDir + EDIT_STEPS_SUBDIR;
|
||||
|
||||
int iBytes = FILEMAN->GetFileSizeInBytes( fn );
|
||||
if( iBytes > MAX_EDIT_SIZE_BYTES )
|
||||
CStringArray vsFiles;
|
||||
GetDirListing( sDir+"*.edit", vsFiles, false, true );
|
||||
|
||||
int iNumEditsLoaded = GetNumEditsLoadedFromProfile( slot );
|
||||
int size = min( (int) vsFiles.size(), MAX_EDITS_PER_PROFILE - iNumEditsLoaded );
|
||||
|
||||
for( int i=0; i<size; i++ )
|
||||
{
|
||||
LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() );
|
||||
continue;
|
||||
}
|
||||
CString fn = vsFiles[i];
|
||||
|
||||
SMLoader::LoadEdit( fn, slot );
|
||||
SMLoader::LoadEdit( fn, slot );
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
//
|
||||
// Load all edit courses
|
||||
//
|
||||
CString sDir = sProfileDir + EDIT_COURSES_SUBDIR;
|
||||
|
||||
CStringArray vsFiles;
|
||||
GetDirListing( sDir+"*.crs", vsFiles, false, true );
|
||||
|
||||
int iNumEditsLoaded = GetNumEditsLoadedFromProfile( slot );
|
||||
int size = min( (int) vsFiles.size(), MAX_EDITS_PER_PROFILE - iNumEditsLoaded );
|
||||
|
||||
for( int i=0; i<size; i++ )
|
||||
{
|
||||
CString fn = vsFiles[i];
|
||||
|
||||
CourseLoaderCRS::LoadEdit( fn, slot );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1308,10 +1341,13 @@ int SongManager::GetNumEditsLoadedFromProfile( ProfileSlot slot ) const
|
||||
|
||||
void SongManager::FreeAllLoadedFromProfile( ProfileSlot slot )
|
||||
{
|
||||
for( unsigned s=0; s<m_pSongs.size(); s++ )
|
||||
FOREACH( Song*, m_pSongs, s )
|
||||
(*s)->FreeAllLoadedFromProfile( slot );
|
||||
|
||||
FOREACH( Course*, m_pCourses, c )
|
||||
{
|
||||
Song* pSong = m_pSongs[s];
|
||||
pSong->FreeAllLoadedFromProfile( slot );
|
||||
if( (*c)->m_LoadedFromProfile == slot )
|
||||
this->DeleteCourse( *c );
|
||||
}
|
||||
|
||||
// After freeing some Steps pointers, the cache will be invalid.
|
||||
|
||||
@@ -59,9 +59,9 @@ public:
|
||||
RageColor GetSongColor( const Song* pSong );
|
||||
RageColor GetDifficultyColor( Difficulty dc ) const;
|
||||
|
||||
CString GetCourseGroupBannerPath( CString sCourseGroup );
|
||||
CString GetCourseGroupBannerPath( const CString &sCourseGroup );
|
||||
void GetCourseGroupNames( CStringArray &AddTo );
|
||||
bool DoesCourseGroupExist( CString sCourseGroup );
|
||||
bool DoesCourseGroupExist( const CString &sCourseGroup );
|
||||
RageColor GetCourseGroupColor( const CString &sCourseGroupName );
|
||||
RageColor GetCourseColor( const Course* pCourse );
|
||||
|
||||
@@ -104,6 +104,7 @@ public:
|
||||
void SortSongs(); // sort m_pSongs
|
||||
|
||||
void UpdateRankingCourses(); // courses shown on the ranking screen
|
||||
void RefreshCourseGroupInfo();
|
||||
|
||||
// Lua
|
||||
void PushSelf( lua_State *L );
|
||||
@@ -131,8 +132,11 @@ protected:
|
||||
vector<Course*> m_pCourses;
|
||||
vector<Course*> m_pBestCourses[NUM_PROFILE_SLOTS][NUM_CourseType];
|
||||
vector<Course*> m_pShuffledCourses; // used by GetRandomCourse
|
||||
CStringArray m_sCourseGroupNames;
|
||||
CStringArray m_sCourseGroupBannerPaths; // each course group may have a banner associated with it
|
||||
struct CourseGroupInfo
|
||||
{
|
||||
CString m_sBannerPath;
|
||||
};
|
||||
map<CString,CourseGroupInfo> m_mapCourseGroupToInfo;
|
||||
|
||||
RageTexturePreloader m_TexturePreload;
|
||||
|
||||
@@ -142,8 +146,6 @@ protected:
|
||||
ThemeMetric1D<RageColor> COURSE_GROUP_COLOR;
|
||||
};
|
||||
|
||||
static const CString EDIT_SUBDIR = "Edits/";
|
||||
|
||||
extern SongManager* SONGMAN; // global and accessable from anywhere in our program
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user