2003-02-16 04:01:45 +00:00
|
|
|
#include "global.h"
|
2002-06-14 22:25:22 +00:00
|
|
|
#include "Course.h"
|
2002-07-23 01:41:40 +00:00
|
|
|
#include "PrefsManager.h"
|
2003-02-16 04:28:17 +00:00
|
|
|
#include "song.h"
|
2002-07-23 01:41:40 +00:00
|
|
|
#include "GameManager.h"
|
|
|
|
|
#include "SongManager.h"
|
2005-07-01 05:07:22 +00:00
|
|
|
#include "SongUtil.h"
|
2004-05-23 00:53:20 +00:00
|
|
|
#include "GameState.h"
|
2002-07-27 19:29:51 +00:00
|
|
|
#include "RageException.h"
|
|
|
|
|
#include "RageLog.h"
|
|
|
|
|
#include "MsdFile.h"
|
2002-07-28 20:28:37 +00:00
|
|
|
#include "PlayerOptions.h"
|
|
|
|
|
#include "SongOptions.h"
|
2005-03-07 22:30:39 +00:00
|
|
|
#include "SongCacheIndex.h"
|
2002-11-11 04:53:31 +00:00
|
|
|
#include "RageUtil.h"
|
2003-02-10 23:17:57 +00:00
|
|
|
#include "TitleSubstitution.h"
|
2003-08-03 00:13:55 +00:00
|
|
|
#include "Steps.h"
|
2003-06-15 02:07:31 +00:00
|
|
|
#include "BannerCache.h"
|
2003-07-22 07:56:46 +00:00
|
|
|
#include "RageFile.h"
|
2003-07-27 14:10:47 +00:00
|
|
|
#include "ThemeManager.h"
|
2003-11-09 22:16:59 +00:00
|
|
|
#include "ProfileManager.h"
|
2004-05-24 03:32:56 +00:00
|
|
|
#include "Foreach.h"
|
2005-03-27 11:52:04 +00:00
|
|
|
#include "UnlockManager.h"
|
2005-03-25 12:37:17 +00:00
|
|
|
#include <limits.h>
|
|
|
|
|
|
2005-06-26 21:31:07 +00:00
|
|
|
|
2005-07-29 23:02:10 +00:00
|
|
|
static const CString CourseTypeNames[] = {
|
|
|
|
|
"Nonstop",
|
|
|
|
|
"Oni",
|
|
|
|
|
"Endless",
|
|
|
|
|
"Survival",
|
|
|
|
|
};
|
|
|
|
|
XToString( CourseType, NUM_CourseType );
|
|
|
|
|
XToThemedString( CourseType, NUM_CourseType );
|
|
|
|
|
|
2005-06-28 08:11:30 +00:00
|
|
|
static const CString SongSortNames[] = {
|
|
|
|
|
"Randomize",
|
|
|
|
|
"MostPlays",
|
|
|
|
|
"FewestPlays",
|
|
|
|
|
"TopGrades",
|
|
|
|
|
"LowestGrades",
|
|
|
|
|
};
|
|
|
|
|
XToString( SongSort, NUM_SongSort );
|
|
|
|
|
XToThemedString( SongSort, NUM_SongSort );
|
|
|
|
|
|
|
|
|
|
|
2003-07-17 22:44:17 +00:00
|
|
|
/* Maximum lower value of ranges when difficult: */
|
|
|
|
|
const int MAX_BOTTOM_RANGE = 10;
|
2003-02-14 21:42:44 +00:00
|
|
|
|
2004-10-07 22:20:52 +00:00
|
|
|
#define SORT_LEVEL1_COLOR THEME->GetMetricC("Course","SortLevel1Color")
|
|
|
|
|
#define SORT_LEVEL2_COLOR THEME->GetMetricC("Course","SortLevel2Color")
|
|
|
|
|
#define SORT_LEVEL3_COLOR THEME->GetMetricC("Course","SortLevel3Color")
|
|
|
|
|
#define SORT_LEVEL4_COLOR THEME->GetMetricC("Course","SortLevel4Color")
|
|
|
|
|
#define SORT_LEVEL5_COLOR THEME->GetMetricC("Course","SortLevel5Color")
|
|
|
|
|
|
2003-12-10 11:35:34 +00:00
|
|
|
|
2002-12-17 05:07:56 +00:00
|
|
|
Course::Course()
|
|
|
|
|
{
|
2004-05-23 00:53:20 +00:00
|
|
|
Init();
|
2002-12-17 05:07:56 +00:00
|
|
|
}
|
2002-07-02 17:34:20 +00:00
|
|
|
|
2005-04-07 08:46:40 +00:00
|
|
|
CourseType Course::GetCourseType() const
|
2003-07-20 01:54:17 +00:00
|
|
|
{
|
2003-07-27 19:14:05 +00:00
|
|
|
if( m_bRepeat )
|
2005-04-07 08:46:40 +00:00
|
|
|
return COURSE_TYPE_ENDLESS;
|
2005-04-21 04:27:13 +00:00
|
|
|
if( m_iLives > 0 )
|
|
|
|
|
return COURSE_TYPE_ONI;
|
2005-06-28 08:11:30 +00:00
|
|
|
FOREACH_CONST( CourseEntry, m_vEntries, e )
|
2005-04-21 04:27:13 +00:00
|
|
|
{
|
|
|
|
|
if( e->fGainSeconds > 0 )
|
|
|
|
|
return COURSE_TYPE_SURVIVAL;
|
|
|
|
|
}
|
|
|
|
|
return COURSE_TYPE_NONSTOP;
|
2005-04-07 08:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PlayMode Course::GetPlayMode() const
|
|
|
|
|
{
|
|
|
|
|
switch( GetCourseType() )
|
|
|
|
|
{
|
|
|
|
|
case COURSE_TYPE_ENDLESS: return PLAY_MODE_ENDLESS;
|
|
|
|
|
case COURSE_TYPE_ONI: return PLAY_MODE_ONI;
|
2005-04-21 04:27:13 +00:00
|
|
|
case COURSE_TYPE_SURVIVAL: return PLAY_MODE_ONI;
|
2005-04-07 08:46:40 +00:00
|
|
|
case COURSE_TYPE_NONSTOP: return PLAY_MODE_NONSTOP;
|
|
|
|
|
default: ASSERT(0); return PLAY_MODE_INVALID;
|
|
|
|
|
}
|
2003-07-20 01:54:17 +00:00
|
|
|
}
|
|
|
|
|
|
2003-02-05 19:16:00 +00:00
|
|
|
void Course::LoadFromCRSFile( CString sPath )
|
2002-07-02 17:34:20 +00:00
|
|
|
{
|
2004-05-23 00:53:20 +00:00
|
|
|
Init();
|
2004-01-26 20:55:35 +00:00
|
|
|
|
2003-01-21 05:14:59 +00:00
|
|
|
m_sPath = sPath; // save path
|
|
|
|
|
|
2005-06-03 01:57:10 +00:00
|
|
|
// 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];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2005-03-07 22:30:39 +00:00
|
|
|
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
|
2005-03-27 11:52:04 +00:00
|
|
|
{
|
2005-03-07 22:30:39 +00:00
|
|
|
LOG->Trace( "Course::LoadFromCRSFile(\"%s\")", sPath.c_str() );
|
2005-03-27 11:52:04 +00:00
|
|
|
}
|
|
|
|
|
|
2002-07-27 19:29:51 +00:00
|
|
|
MsdFile msd;
|
|
|
|
|
if( !msd.ReadFile(sPath) )
|
2003-04-25 00:01:35 +00:00
|
|
|
RageException::Throw( "Error opening CRS file '%s'.", sPath.c_str() );
|
2002-07-02 17:34:20 +00:00
|
|
|
|
2005-03-08 01:07:01 +00:00
|
|
|
const CString sFName = SetExtension( m_sPath, "" );
|
2002-07-02 17:34:20 +00:00
|
|
|
|
2002-07-27 19:29:51 +00:00
|
|
|
CStringArray arrayPossibleBanners;
|
2005-03-08 01:07:01 +00:00
|
|
|
GetDirListing( sFName + "*.png", arrayPossibleBanners, false, true );
|
|
|
|
|
GetDirListing( sFName + "*.jpg", arrayPossibleBanners, false, true );
|
|
|
|
|
GetDirListing( sFName + "*.bmp", arrayPossibleBanners, false, true );
|
|
|
|
|
GetDirListing( sFName + "*.gif", arrayPossibleBanners, false, true );
|
2002-10-31 02:11:52 +00:00
|
|
|
if( !arrayPossibleBanners.empty() )
|
2002-07-27 19:29:51 +00:00
|
|
|
m_sBannerPath = arrayPossibleBanners[0];
|
2002-07-02 17:34:20 +00:00
|
|
|
|
2003-10-26 03:02:30 +00:00
|
|
|
AttackArray attacks;
|
2005-04-21 04:27:13 +00:00
|
|
|
float fGainSeconds = 0;
|
2003-01-14 22:10:04 +00:00
|
|
|
for( unsigned i=0; i<msd.GetNumValues(); i++ )
|
2002-07-02 17:34:20 +00:00
|
|
|
{
|
2003-01-14 22:44:30 +00:00
|
|
|
CString sValueName = msd.GetParam(i, 0);
|
|
|
|
|
const MsdFile::value_t &sParams = msd.GetValue(i);
|
2002-07-02 17:34:20 +00:00
|
|
|
|
2002-07-27 19:29:51 +00:00
|
|
|
// handle the data
|
|
|
|
|
if( 0 == stricmp(sValueName, "COURSE") )
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitle = sParams[1];
|
2004-05-25 04:18:59 +00:00
|
|
|
else if( 0 == stricmp(sValueName, "COURSETRANSLIT") )
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitleTranslit = sParams[1];
|
2002-07-27 19:29:51 +00:00
|
|
|
else if( 0 == stricmp(sValueName, "REPEAT") )
|
2002-07-02 17:34:20 +00:00
|
|
|
{
|
2003-01-14 22:44:30 +00:00
|
|
|
CString str = sParams[1];
|
|
|
|
|
str.MakeLower();
|
|
|
|
|
if( str.Find("yes") != -1 )
|
2002-07-27 19:29:51 +00:00
|
|
|
m_bRepeat = true;
|
2002-07-02 17:34:20 +00:00
|
|
|
}
|
|
|
|
|
|
2002-07-27 19:29:51 +00:00
|
|
|
else if( 0 == stricmp(sValueName, "LIVES") )
|
|
|
|
|
m_iLives = atoi( sParams[1] );
|
2002-07-02 17:34:20 +00:00
|
|
|
|
2005-04-21 04:27:13 +00:00
|
|
|
else if( 0 == stricmp(sValueName, "GAINSECONDS") )
|
2005-04-24 19:27:30 +00:00
|
|
|
fGainSeconds = strtof( sParams[1], NULL );
|
2005-04-21 04:27:13 +00:00
|
|
|
|
2003-07-21 22:33:01 +00:00
|
|
|
else if( 0 == stricmp(sValueName, "METER") )
|
2004-03-04 22:24:09 +00:00
|
|
|
{
|
|
|
|
|
if( sParams.params.size() == 2 )
|
2004-06-04 02:05:56 +00:00
|
|
|
m_iCustomMeter[DIFFICULTY_MEDIUM] = atoi( sParams[1] ); /* compat */
|
2004-03-04 22:24:09 +00:00
|
|
|
else if( sParams.params.size() == 3 )
|
|
|
|
|
{
|
|
|
|
|
const CourseDifficulty cd = StringToCourseDifficulty( sParams[1] );
|
2004-06-04 02:05:56 +00:00
|
|
|
if( cd == DIFFICULTY_INVALID )
|
2004-03-04 22:24:09 +00:00
|
|
|
{
|
|
|
|
|
LOG->Warn( "Course file '%s' contains an invalid #METER string: \"%s\"",
|
2005-03-08 01:07:01 +00:00
|
|
|
sPath.c_str(), sParams[1].c_str() );
|
2004-03-04 22:24:09 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
2004-05-23 00:53:20 +00:00
|
|
|
m_iCustomMeter[cd] = atoi( sParams[2] );
|
2004-03-04 22:24:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
2003-07-21 22:33:01 +00:00
|
|
|
|
2003-10-26 03:02:30 +00:00
|
|
|
else if( 0 == stricmp(sValueName, "MODS") )
|
|
|
|
|
{
|
|
|
|
|
Attack attack;
|
2003-11-17 17:54:53 +00:00
|
|
|
float end = -9999;
|
2003-10-26 03:02:30 +00:00
|
|
|
for( unsigned j = 1; j < sParams.params.size(); ++j )
|
|
|
|
|
{
|
|
|
|
|
CStringArray sBits;
|
|
|
|
|
split( sParams[j], "=", sBits, false );
|
2004-09-08 00:33:20 +00:00
|
|
|
if( sBits.size() < 2 )
|
2003-10-26 03:02:30 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
TrimLeft( sBits[0] );
|
|
|
|
|
TrimRight( sBits[0] );
|
|
|
|
|
if( !sBits[0].CompareNoCase("TIME") )
|
2004-08-10 20:57:59 +00:00
|
|
|
attack.fStartSecond = strtof( sBits[1], NULL );
|
2003-10-26 03:02:30 +00:00
|
|
|
else if( !sBits[0].CompareNoCase("LEN") )
|
2004-08-10 20:57:59 +00:00
|
|
|
attack.fSecsRemaining = strtof( sBits[1], NULL );
|
2003-11-17 17:54:53 +00:00
|
|
|
else if( !sBits[0].CompareNoCase("END") )
|
2004-08-10 20:57:59 +00:00
|
|
|
end = strtof( sBits[1], NULL );
|
2003-10-26 03:02:30 +00:00
|
|
|
else if( !sBits[0].CompareNoCase("MODS") )
|
2003-11-18 00:51:15 +00:00
|
|
|
{
|
2005-05-20 08:57:59 +00:00
|
|
|
attack.sModifiers = sBits[1];
|
2003-11-18 00:51:15 +00:00
|
|
|
if( end != -9999 )
|
|
|
|
|
{
|
2005-04-22 05:15:10 +00:00
|
|
|
ASSERT_M( end >= attack.fStartSecond, ssprintf("Attack ends before it starts. end %f, start %f", end, attack.fStartSecond) );
|
2003-11-18 00:51:15 +00:00
|
|
|
attack.fSecsRemaining = end - attack.fStartSecond;
|
|
|
|
|
end = -9999;
|
|
|
|
|
}
|
2005-05-02 02:41:00 +00:00
|
|
|
|
|
|
|
|
// warn on invalid so we catch bogus mods on load
|
|
|
|
|
PlayerOptions po;
|
2005-05-20 08:57:59 +00:00
|
|
|
po.FromString( attack.sModifiers, true );
|
2005-05-02 02:41:00 +00:00
|
|
|
|
2003-11-18 00:51:15 +00:00
|
|
|
attacks.push_back( attack );
|
|
|
|
|
}
|
2005-04-22 06:39:35 +00:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LOG->Warn( "Unexpected value named '%s'", sBits[0].c_str() );
|
|
|
|
|
}
|
2003-10-26 03:02:30 +00:00
|
|
|
}
|
2003-11-17 17:54:53 +00:00
|
|
|
|
|
|
|
|
|
2003-10-26 03:02:30 +00:00
|
|
|
}
|
2002-07-27 19:29:51 +00:00
|
|
|
else if( 0 == stricmp(sValueName, "SONG") )
|
2002-07-02 17:34:20 +00:00
|
|
|
{
|
2003-08-10 23:48:10 +00:00
|
|
|
CourseEntry new_entry;
|
2003-02-16 10:12:03 +00:00
|
|
|
|
2003-02-16 23:54:30 +00:00
|
|
|
// infer entry::Type from the first param
|
2003-04-14 07:11:04 +00:00
|
|
|
if( sParams[1].Left(strlen("BEST")) == "BEST" )
|
2003-02-16 10:12:03 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1;
|
|
|
|
|
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
|
|
|
|
new_entry.songSort = SongSort_MostPlays;
|
2003-02-16 10:12:03 +00:00
|
|
|
}
|
2003-04-14 07:11:04 +00:00
|
|
|
else if( sParams[1].Left(strlen("WORST")) == "WORST" )
|
2003-02-16 10:12:03 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1;
|
|
|
|
|
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
|
|
|
|
new_entry.songSort = SongSort_FewestPlays;
|
2003-02-16 10:12:03 +00:00
|
|
|
}
|
|
|
|
|
else if( sParams[1] == "*" )
|
|
|
|
|
{
|
2005-03-10 22:54:55 +00:00
|
|
|
new_entry.bSecret = true;
|
2003-02-16 10:12:03 +00:00
|
|
|
}
|
|
|
|
|
else if( sParams[1].Right(1) == "*" )
|
|
|
|
|
{
|
2005-03-10 22:54:55 +00:00
|
|
|
new_entry.bSecret = true;
|
2003-02-16 23:54:30 +00:00
|
|
|
CString sSong = sParams[1];
|
|
|
|
|
sSong.Replace( "\\", "/" );
|
|
|
|
|
CStringArray bits;
|
|
|
|
|
split( sSong, "/", bits );
|
|
|
|
|
if( bits.size() == 2 )
|
2005-06-28 08:11:30 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
new_entry.sSongGroup = bits[0];
|
2005-06-28 08:11:30 +00:00
|
|
|
}
|
2003-02-16 23:54:30 +00:00
|
|
|
else
|
2005-06-28 08:11:30 +00:00
|
|
|
{
|
2003-02-16 23:54:30 +00:00
|
|
|
LOG->Warn( "Course file '%s' contains a random_within_group entry '%s' that is invalid. "
|
|
|
|
|
"Song should be in the format '<group>/*'.",
|
2005-03-08 01:07:01 +00:00
|
|
|
sPath.c_str(), sSong.c_str());
|
2005-06-28 08:11:30 +00:00
|
|
|
}
|
|
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
if( !SONGMAN->DoesSongGroupExist(new_entry.sSongGroup) )
|
2003-02-16 23:54:30 +00:00
|
|
|
{
|
2003-06-15 01:24:12 +00:00
|
|
|
/* 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. */
|
2003-09-22 01:06:07 +00:00
|
|
|
LOG->Trace( "Course file '%s' random_within_group entry '%s' specifies a group that doesn't exist. "
|
|
|
|
|
"This entry will be ignored.",
|
2005-03-08 01:07:01 +00:00
|
|
|
sPath.c_str(), sSong.c_str());
|
2003-02-16 23:54:30 +00:00
|
|
|
continue; // skip this #SONG
|
|
|
|
|
}
|
2003-02-16 10:12:03 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2003-02-16 23:54:30 +00:00
|
|
|
CString sSong = sParams[1];
|
2003-07-22 05:45:30 +00:00
|
|
|
new_entry.pSong = SONGMAN->FindSong( sSong );
|
|
|
|
|
|
|
|
|
|
if( new_entry.pSong == NULL )
|
2003-02-16 23:54:30 +00:00
|
|
|
{
|
2003-06-15 01:24:12 +00:00
|
|
|
/* 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. */
|
2003-07-22 05:45:30 +00:00
|
|
|
LOG->Trace( "Course file '%s' contains a fixed song entry '%s' that does not exist. "
|
|
|
|
|
"This entry will be ignored.",
|
2005-03-08 01:07:01 +00:00
|
|
|
sPath.c_str(), sSong.c_str());
|
2003-02-16 23:54:30 +00:00
|
|
|
continue; // skip this #SONG
|
|
|
|
|
}
|
2003-02-16 10:12:03 +00:00
|
|
|
}
|
|
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
new_entry.baseDifficulty = StringToDifficulty( sParams[2] );
|
|
|
|
|
if( new_entry.baseDifficulty == DIFFICULTY_INVALID )
|
2003-02-16 10:12:03 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
int retval = sscanf( sParams[2], "%d..%d", &new_entry.iLowMeter, &new_entry.iHighMeter );
|
2003-05-30 09:19:43 +00:00
|
|
|
if( retval == 1 )
|
2005-06-27 04:41:36 +00:00
|
|
|
new_entry.iHighMeter = new_entry.iLowMeter;
|
2003-05-30 09:19:43 +00:00
|
|
|
else if( retval != 2 )
|
2003-02-16 10:12:03 +00:00
|
|
|
{
|
2003-05-30 09:19:43 +00:00
|
|
|
LOG->Warn("Course file '%s' contains an invalid difficulty setting: \"%s\", 3..6 used instead",
|
2005-03-08 01:07:01 +00:00
|
|
|
sPath.c_str(), sParams[2].c_str());
|
2005-06-27 04:41:36 +00:00
|
|
|
new_entry.iLowMeter = 3;
|
|
|
|
|
new_entry.iHighMeter = 6;
|
2003-02-16 10:12:03 +00:00
|
|
|
}
|
2002-10-12 19:30:15 +00:00
|
|
|
}
|
2003-02-16 10:12:03 +00:00
|
|
|
|
2003-07-30 21:33:20 +00:00
|
|
|
{
|
2005-03-10 22:54:55 +00:00
|
|
|
/* If "showcourse" or "noshowcourse" is in the list, force new_entry.secret
|
2003-07-30 21:33:20 +00:00
|
|
|
* on or off. */
|
|
|
|
|
CStringArray mods;
|
|
|
|
|
split( sParams[3], ",", mods, true );
|
|
|
|
|
for( int j = (int) mods.size()-1; j >= 0 ; --j )
|
|
|
|
|
{
|
2005-05-04 03:45:43 +00:00
|
|
|
CString &sMod = mods[j];
|
|
|
|
|
TrimLeft( sMod );
|
|
|
|
|
TrimRight( sMod );
|
|
|
|
|
if( !sMod.CompareNoCase("showcourse") )
|
2005-03-10 22:54:55 +00:00
|
|
|
new_entry.bSecret = false;
|
2005-05-04 03:45:43 +00:00
|
|
|
else if( !sMod.CompareNoCase("noshowcourse") )
|
2005-03-10 22:54:55 +00:00
|
|
|
new_entry.bSecret = true;
|
2005-05-04 03:45:43 +00:00
|
|
|
else if( !sMod.CompareNoCase("nodifficult") )
|
2005-06-27 04:41:36 +00:00
|
|
|
new_entry.bNoDifficult = true;
|
2003-07-30 21:33:20 +00:00
|
|
|
else
|
|
|
|
|
continue;
|
|
|
|
|
mods.erase(mods.begin() + j);
|
|
|
|
|
}
|
2005-06-27 04:41:36 +00:00
|
|
|
new_entry.sModifiers = join( ",", mods );
|
2003-07-30 21:33:20 +00:00
|
|
|
}
|
2003-02-16 10:12:03 +00:00
|
|
|
|
2003-10-26 03:02:30 +00:00
|
|
|
new_entry.attacks = attacks;
|
2005-04-21 04:27:13 +00:00
|
|
|
new_entry.fGainSeconds = fGainSeconds;
|
2003-10-26 03:17:45 +00:00
|
|
|
attacks.clear();
|
2003-10-26 03:02:30 +00:00
|
|
|
|
2005-06-28 08:11:30 +00:00
|
|
|
m_vEntries.push_back( new_entry );
|
2002-07-02 17:34:20 +00:00
|
|
|
}
|
2005-03-07 22:30:39 +00:00
|
|
|
else if( bUseCache && !stricmp(sValueName, "RADAR") )
|
|
|
|
|
{
|
|
|
|
|
StepsType st = (StepsType) atoi(sParams[1]);
|
|
|
|
|
CourseDifficulty cd = (CourseDifficulty) atoi(sParams[2]);
|
2002-07-02 17:34:20 +00:00
|
|
|
|
2005-03-07 22:30:39 +00:00
|
|
|
RadarValues rv;
|
|
|
|
|
rv.FromString( sParams[3] );
|
|
|
|
|
m_RadarCache[CacheEntry(st, cd)] = rv;
|
|
|
|
|
}
|
2002-07-02 17:34:20 +00:00
|
|
|
else
|
2005-03-10 19:57:43 +00:00
|
|
|
{
|
2005-04-22 06:39:35 +00:00
|
|
|
LOG->Warn( "Unexpected value named '%s'", sValueName.c_str() );
|
2005-03-10 19:57:43 +00:00
|
|
|
}
|
2002-07-02 17:34:20 +00:00
|
|
|
}
|
2003-02-18 20:34:38 +00:00
|
|
|
static TitleSubst tsub("courses");
|
2003-02-10 23:17:57 +00:00
|
|
|
|
2004-05-31 00:59:33 +00:00
|
|
|
TitleFields title;
|
2004-07-11 10:02:38 +00:00
|
|
|
title.Title = m_sMainTitle;
|
|
|
|
|
title.TitleTranslit = m_sMainTitleTranslit;
|
2004-05-31 00:59:33 +00:00
|
|
|
tsub.Subst( title );
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitle = title.Title;
|
|
|
|
|
m_sMainTitleTranslit = title.TitleTranslit;
|
2004-12-02 23:04:15 +00:00
|
|
|
|
|
|
|
|
/* Cache and load the course banner. Only bother doing this if at least one
|
|
|
|
|
* song was found in the course. */
|
2005-06-28 08:11:30 +00:00
|
|
|
if( m_sBannerPath != "" && !m_vEntries.empty() )
|
2004-12-02 23:04:15 +00:00
|
|
|
BANNERCACHE->CacheBanner( m_sBannerPath );
|
2005-03-07 22:30:39 +00:00
|
|
|
|
|
|
|
|
/* Cache each trail RadarValues that's slow to load, so we
|
|
|
|
|
* don't have to do it at runtime. */
|
|
|
|
|
if( !bUseCache )
|
|
|
|
|
{
|
2005-03-27 11:52:04 +00:00
|
|
|
CalculateRadarValues();
|
2005-03-07 22:30:39 +00:00
|
|
|
|
|
|
|
|
/* 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) );
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-07-02 17:34:20 +00:00
|
|
|
}
|
2002-07-23 01:41:40 +00:00
|
|
|
|
2004-07-24 06:40:51 +00:00
|
|
|
void Course::RevertFromDisk()
|
|
|
|
|
{
|
2004-08-12 04:49:15 +00:00
|
|
|
// trying to catch invalid an Course
|
|
|
|
|
ASSERT( !m_sPath.empty() );
|
|
|
|
|
|
2004-07-24 06:40:51 +00:00
|
|
|
LoadFromCRSFile( m_sPath );
|
|
|
|
|
}
|
|
|
|
|
|
2005-03-07 22:30:39 +00:00
|
|
|
CString Course::GetCacheFilePath() const
|
|
|
|
|
{
|
|
|
|
|
return SongCacheIndex::GetCacheFilePath( "Courses", m_sPath );
|
|
|
|
|
}
|
|
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
void Course::Init()
|
2004-01-26 20:55:35 +00:00
|
|
|
{
|
|
|
|
|
m_bIsAutogen = false;
|
|
|
|
|
m_bRepeat = false;
|
|
|
|
|
m_bRandomize = false;
|
|
|
|
|
m_iLives = -1;
|
2004-05-25 02:17:17 +00:00
|
|
|
m_bSortByMeter = false;
|
2005-06-28 08:11:30 +00:00
|
|
|
m_vEntries.clear();
|
2004-06-04 02:52:07 +00:00
|
|
|
FOREACH_Difficulty(dc)
|
|
|
|
|
m_iCustomMeter[dc] = -1;
|
2005-06-28 08:11:30 +00:00
|
|
|
m_vEntries.clear();
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sPath = "";
|
2005-06-03 01:57:10 +00:00
|
|
|
m_sGroupName = "";
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitle = "";
|
|
|
|
|
m_sMainTitleTranslit = "";
|
|
|
|
|
m_sSubTitle = "";
|
|
|
|
|
m_sSubTitleTranslit = "";
|
|
|
|
|
m_sBannerPath = "";
|
|
|
|
|
m_sCDTitlePath = "";
|
2005-07-29 02:23:02 +00:00
|
|
|
m_LoadedFromProfile = PROFILE_SLOT_INVALID;
|
2004-06-11 21:13:05 +00:00
|
|
|
m_iTrailCacheSeed = 0;
|
2004-01-26 20:55:35 +00:00
|
|
|
}
|
2002-07-23 01:41:40 +00:00
|
|
|
|
2005-03-07 22:30:39 +00:00
|
|
|
void Course::Save( CString sPath, bool bSavingCache )
|
2003-04-11 00:12:22 +00:00
|
|
|
{
|
|
|
|
|
ASSERT( !m_bIsAutogen );
|
|
|
|
|
|
2005-03-07 22:30:39 +00:00
|
|
|
/* By default, save to the file we loaded from. */
|
|
|
|
|
if( sPath == "" )
|
|
|
|
|
sPath = m_sPath;
|
|
|
|
|
|
2003-12-04 19:13:29 +00:00
|
|
|
RageFile f;
|
2005-03-07 22:30:39 +00:00
|
|
|
if( !f.Open( sPath, RageFile::WRITE ) )
|
2003-04-11 00:12:22 +00:00
|
|
|
{
|
2005-03-07 22:30:39 +00:00
|
|
|
LOG->Warn( "Could not write course file '%s': %s", sPath.c_str(), f.GetError().c_str() );
|
2003-04-11 00:12:22 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2004-07-11 10:02:38 +00:00
|
|
|
f.PutLine( ssprintf("#COURSE:%s;", m_sMainTitle.c_str()) );
|
|
|
|
|
if( m_sMainTitleTranslit != "" )
|
|
|
|
|
f.PutLine( ssprintf("#COURSETRANSLIT:%s;", m_sMainTitleTranslit.c_str()) );
|
2003-09-05 21:05:05 +00:00
|
|
|
if( m_bRepeat )
|
2003-12-04 19:13:29 +00:00
|
|
|
f.PutLine( "#REPEAT:YES;" );
|
2003-09-05 21:05:05 +00:00
|
|
|
if( m_iLives != -1 )
|
2003-12-04 19:13:29 +00:00
|
|
|
f.PutLine( ssprintf("#LIVES:%i;", m_iLives) );
|
2005-03-10 20:49:06 +00:00
|
|
|
|
2004-03-04 22:24:09 +00:00
|
|
|
FOREACH_CourseDifficulty( cd )
|
|
|
|
|
{
|
2004-05-23 00:53:20 +00:00
|
|
|
if( m_iCustomMeter[cd] == -1 )
|
2004-03-04 22:24:09 +00:00
|
|
|
continue;
|
2004-05-23 00:53:20 +00:00
|
|
|
f.PutLine( ssprintf("#METER:%s:%i;", CourseDifficultyToString(cd).c_str(), m_iCustomMeter[cd]) );
|
2004-03-04 22:24:09 +00:00
|
|
|
}
|
2003-04-11 00:12:22 +00:00
|
|
|
|
2005-03-07 22:30:39 +00:00
|
|
|
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" );
|
|
|
|
|
}
|
|
|
|
|
|
2005-06-28 08:11:30 +00:00
|
|
|
for( unsigned i=0; i<m_vEntries.size(); i++ )
|
2003-04-11 00:12:22 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
const CourseEntry& entry = m_vEntries[i];
|
2003-04-11 00:12:22 +00:00
|
|
|
|
2003-10-26 03:17:45 +00:00
|
|
|
for( unsigned j = 0; j < entry.attacks.size(); ++j )
|
|
|
|
|
{
|
|
|
|
|
if( j == 0 )
|
2003-12-04 19:13:29 +00:00
|
|
|
f.PutLine( "#MODS:" );
|
2003-10-26 03:17:45 +00:00
|
|
|
|
|
|
|
|
const Attack &a = entry.attacks[j];
|
2005-06-27 04:41:36 +00:00
|
|
|
f.Write( ssprintf( " TIME=%.2f:LEN=%.2f:MODS=%s",
|
|
|
|
|
a.fStartSecond, a.fSecsRemaining, a.sModifiers.c_str() ) );
|
2003-10-26 03:17:45 +00:00
|
|
|
|
|
|
|
|
if( j+1 < entry.attacks.size() )
|
2005-06-27 04:41:36 +00:00
|
|
|
f.Write( ":" );
|
2003-10-26 03:17:45 +00:00
|
|
|
else
|
2005-06-27 04:41:36 +00:00
|
|
|
f.Write( ";" );
|
|
|
|
|
f.PutLine( "" );
|
2003-10-26 03:17:45 +00:00
|
|
|
}
|
|
|
|
|
|
2005-04-27 03:07:05 +00:00
|
|
|
if( entry.fGainSeconds > 0 )
|
|
|
|
|
f.PutLine( ssprintf("#GAINSECONDS:%f;", entry.fGainSeconds) );
|
|
|
|
|
|
2005-06-28 08:11:30 +00:00
|
|
|
if( entry.songSort == SongSort_MostPlays && entry.iChooseIndex != -1 )
|
2003-04-11 00:12:22 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
f.Write( ssprintf( "#SONG:BEST%d", entry.iChooseIndex+1 ) );
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
2005-06-28 08:11:30 +00:00
|
|
|
else if( entry.songSort == SongSort_FewestPlays && entry.iChooseIndex != -1 )
|
2005-06-27 04:41:36 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
f.Write( ssprintf( "#SONG:WORST%d", entry.iChooseIndex+1 ) );
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
|
|
|
|
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:*" );
|
2003-04-11 00:12:22 +00:00
|
|
|
}
|
|
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
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( ":" );
|
2003-07-30 21:33:20 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
CString sModifiers = entry.sModifiers;
|
|
|
|
|
bool bDefaultSecret = entry.IsRandomSong();
|
|
|
|
|
if( bDefaultSecret != entry.bSecret )
|
2003-07-30 21:33:20 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
if( sModifiers != "" )
|
|
|
|
|
sModifiers += ",";
|
|
|
|
|
sModifiers += entry.bSecret? "noshowcourse":"showcourse";
|
2004-02-10 22:52:43 +00:00
|
|
|
}
|
|
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
if( entry.bNoDifficult )
|
2004-02-10 22:52:43 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
if( sModifiers != "" )
|
|
|
|
|
sModifiers += ",";
|
|
|
|
|
sModifiers += "nodifficult";
|
2003-07-30 21:33:20 +00:00
|
|
|
}
|
2005-06-27 04:41:36 +00:00
|
|
|
f.Write( sModifiers );
|
2003-04-11 00:12:22 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
f.PutLine( ";" );
|
2003-04-11 00:12:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2004-05-20 23:14:36 +00:00
|
|
|
void Course::AutogenEndlessFromGroup( CString sGroupName, Difficulty diff )
|
2002-07-23 01:41:40 +00:00
|
|
|
{
|
2003-03-27 01:56:21 +00:00
|
|
|
m_bIsAutogen = true;
|
2002-07-29 03:06:55 +00:00
|
|
|
m_bRepeat = true;
|
|
|
|
|
m_bRandomize = true;
|
|
|
|
|
m_iLives = -1;
|
2004-06-04 02:52:07 +00:00
|
|
|
FOREACH_Difficulty(dc)
|
|
|
|
|
m_iCustomMeter[dc] = -1;
|
2002-08-01 20:30:40 +00:00
|
|
|
|
2004-05-20 23:14:36 +00:00
|
|
|
if( sGroupName == "" )
|
|
|
|
|
{
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitle = "All Songs";
|
2004-05-20 23:14:36 +00:00
|
|
|
// m_sBannerPath = ""; // XXX
|
2005-06-23 08:05:09 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitle = SONGMAN->ShortenGroupName( sGroupName );
|
2005-06-23 08:05:09 +00:00
|
|
|
m_sBannerPath = SONGMAN->GetSongGroupBannerPath( sGroupName );
|
2004-05-20 23:14:36 +00:00
|
|
|
}
|
2002-07-23 01:41:40 +00:00
|
|
|
|
2003-02-24 00:58:04 +00:00
|
|
|
// We want multiple songs, so we can try to prevent repeats during
|
|
|
|
|
// gameplay. (We might still get a repeat at the repeat boundary,
|
|
|
|
|
// but that'd be rare.) -glenn
|
2003-08-10 23:48:10 +00:00
|
|
|
CourseEntry e;
|
2005-06-27 04:41:36 +00:00
|
|
|
e.sSongGroup = sGroupName;
|
|
|
|
|
e.baseDifficulty = diff;
|
2005-03-10 22:54:55 +00:00
|
|
|
e.bSecret = true;
|
2003-02-24 00:58:04 +00:00
|
|
|
|
|
|
|
|
vector<Song*> vSongs;
|
2005-06-27 04:41:36 +00:00
|
|
|
SONGMAN->GetSongs( vSongs, e.sSongGroup );
|
2003-02-24 00:58:04 +00:00
|
|
|
for( unsigned i = 0; i < vSongs.size(); ++i)
|
2005-06-28 08:11:30 +00:00
|
|
|
m_vEntries.push_back( e );
|
2002-07-23 01:41:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-05-20 23:14:36 +00:00
|
|
|
void Course::AutogenNonstopFromGroup( CString sGroupName, Difficulty diff )
|
2004-05-20 03:26:36 +00:00
|
|
|
{
|
2004-05-20 23:14:36 +00:00
|
|
|
AutogenEndlessFromGroup( sGroupName, diff );
|
2003-03-27 01:56:21 +00:00
|
|
|
|
|
|
|
|
m_bRepeat = false;
|
|
|
|
|
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitle += " Random";
|
2003-03-27 01:56:21 +00:00
|
|
|
|
|
|
|
|
// resize to 4
|
2005-06-28 08:11:30 +00:00
|
|
|
while( m_vEntries.size() < 4 )
|
|
|
|
|
m_vEntries.push_back( m_vEntries[0] );
|
|
|
|
|
while( m_vEntries.size() > 4 )
|
|
|
|
|
m_vEntries.pop_back();
|
2003-03-27 01:56:21 +00:00
|
|
|
}
|
|
|
|
|
|
2004-05-25 04:08:21 +00:00
|
|
|
void Course::AutogenOniFromArtist( CString sArtistName, CString sArtistNameTranslit, vector<Song*> aSongs, Difficulty dc )
|
2004-05-25 02:17:17 +00:00
|
|
|
{
|
|
|
|
|
m_bIsAutogen = true;
|
|
|
|
|
m_bRepeat = false;
|
|
|
|
|
m_bRandomize = true;
|
|
|
|
|
m_bSortByMeter = true;
|
|
|
|
|
|
|
|
|
|
m_iLives = 4;
|
2004-06-04 02:52:07 +00:00
|
|
|
FOREACH_Difficulty(cd)
|
|
|
|
|
m_iCustomMeter[cd] = -1;
|
2004-05-25 02:17:17 +00:00
|
|
|
|
|
|
|
|
ASSERT( sArtistName != "" );
|
|
|
|
|
ASSERT( aSongs.size() > 0 );
|
|
|
|
|
|
|
|
|
|
/* "Artist Oni" is a little repetitive; "by Artist" stands out less, and lowercasing
|
|
|
|
|
* "by" puts more emphasis on the artist's name. It also sorts them together. */
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitle = "by " + sArtistName;
|
2004-05-25 04:08:21 +00:00
|
|
|
if( sArtistNameTranslit != sArtistName )
|
2004-07-11 10:02:38 +00:00
|
|
|
m_sMainTitleTranslit = "by " + sArtistNameTranslit;
|
2004-05-25 04:08:21 +00:00
|
|
|
|
2004-05-25 02:17:17 +00:00
|
|
|
|
|
|
|
|
// m_sBannerPath = ""; // XXX
|
|
|
|
|
|
|
|
|
|
/* Shuffle the list to determine which songs we'll use. Shuffle it deterministically,
|
|
|
|
|
* so we always get the same set of songs unless the song set changes. */
|
|
|
|
|
{
|
|
|
|
|
RandomGen rng( GetHashForString( sArtistName ) + aSongs.size() );
|
|
|
|
|
random_shuffle( aSongs.begin(), aSongs.end(), rng );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Only use up to four songs. */
|
|
|
|
|
if( aSongs.size() > 4 )
|
|
|
|
|
aSongs.erase( aSongs.begin()+4, aSongs.end() );
|
|
|
|
|
|
|
|
|
|
CourseEntry e;
|
2005-06-27 04:41:36 +00:00
|
|
|
e.baseDifficulty = dc;
|
2004-05-25 02:17:17 +00:00
|
|
|
|
|
|
|
|
for( unsigned i = 0; i < aSongs.size(); ++i )
|
|
|
|
|
{
|
|
|
|
|
e.pSong = aSongs[i];
|
2005-06-28 08:11:30 +00:00
|
|
|
m_vEntries.push_back( e );
|
2004-05-25 02:17:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-05-24 03:32:56 +00:00
|
|
|
bool Course::IsPlayableIn( StepsType st ) const
|
2003-02-16 10:12:03 +00:00
|
|
|
{
|
2004-06-04 02:05:56 +00:00
|
|
|
return GetTrail( st ) != NULL;
|
2003-02-16 10:12:03 +00:00
|
|
|
}
|
2003-02-14 21:42:44 +00:00
|
|
|
|
2003-08-04 21:34:45 +00:00
|
|
|
|
2004-05-25 02:17:17 +00:00
|
|
|
struct SortTrailEntry
|
|
|
|
|
{
|
|
|
|
|
TrailEntry entry;
|
|
|
|
|
int SortMeter;
|
|
|
|
|
bool operator< ( const SortTrailEntry &rhs ) const { return SortMeter < rhs.SortMeter; }
|
|
|
|
|
};
|
|
|
|
|
|
2004-07-11 10:02:38 +00:00
|
|
|
CString Course::GetDisplayMainTitle() const
|
2004-05-25 03:44:47 +00:00
|
|
|
{
|
2004-12-04 06:09:30 +00:00
|
|
|
if( !PREFSMAN->m_bShowNativeLanguage )
|
2004-07-11 10:02:38 +00:00
|
|
|
return GetTranslitMainTitle();
|
|
|
|
|
return m_sMainTitle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CString Course::GetDisplaySubTitle() const
|
|
|
|
|
{
|
2004-12-04 06:09:30 +00:00
|
|
|
if( !PREFSMAN->m_bShowNativeLanguage )
|
2004-07-11 10:02:38 +00:00
|
|
|
return GetTranslitSubTitle();
|
|
|
|
|
return m_sSubTitle;
|
|
|
|
|
}
|
|
|
|
|
|
2005-05-23 00:38:09 +00:00
|
|
|
CString Course::GetDisplayFullTitle() const
|
2004-07-11 10:02:38 +00:00
|
|
|
{
|
|
|
|
|
CString Title = GetDisplayMainTitle();
|
|
|
|
|
CString SubTitle = GetDisplaySubTitle();
|
|
|
|
|
|
|
|
|
|
if(!SubTitle.empty()) Title += " " + SubTitle;
|
|
|
|
|
return Title;
|
|
|
|
|
}
|
|
|
|
|
|
2005-05-23 00:38:09 +00:00
|
|
|
CString Course::GetTranslitFullTitle() const
|
2004-07-11 10:02:38 +00:00
|
|
|
{
|
|
|
|
|
CString Title = GetTranslitMainTitle();
|
|
|
|
|
CString SubTitle = GetTranslitSubTitle();
|
|
|
|
|
|
|
|
|
|
if(!SubTitle.empty()) Title += " " + SubTitle;
|
|
|
|
|
return Title;
|
2004-05-25 03:44:47 +00:00
|
|
|
}
|
|
|
|
|
|
2003-12-21 02:54:23 +00:00
|
|
|
/* This is called by many simple functions, like Course::GetTotalSeconds, and may
|
|
|
|
|
* be called on all songs to sort. It can take time to execute, so we cache the
|
2004-06-03 21:45:24 +00:00
|
|
|
* results. Returned pointers remain valid for the lifetime of the Course. If the
|
|
|
|
|
* course difficulty doesn't exist, NULL is returned. */
|
2004-05-24 03:32:56 +00:00
|
|
|
Trail* Course::GetTrail( StepsType st, CourseDifficulty cd ) const
|
2002-07-23 01:41:40 +00:00
|
|
|
{
|
2004-06-04 02:05:56 +00:00
|
|
|
ASSERT( cd != DIFFICULTY_INVALID );
|
2004-06-03 08:22:02 +00:00
|
|
|
|
2004-06-11 21:13:05 +00:00
|
|
|
//
|
|
|
|
|
// Check to see if the Trail cache is out of date
|
|
|
|
|
//
|
2005-03-11 18:09:34 +00:00
|
|
|
if( m_iTrailCacheSeed != GAMESTATE->m_iStageSeed )
|
2004-06-11 21:13:05 +00:00
|
|
|
{
|
|
|
|
|
/* If we have any random entries (so that the seed matters), invalidate the cache. */
|
|
|
|
|
bool bHaveRandom = false;
|
2005-06-28 08:11:30 +00:00
|
|
|
FOREACH_CONST( CourseEntry, m_vEntries, e )
|
2005-06-27 04:41:36 +00:00
|
|
|
{
|
|
|
|
|
if( e->IsRandomSong() )
|
|
|
|
|
{
|
2004-06-11 21:13:05 +00:00
|
|
|
bHaveRandom = true;
|
2005-06-27 04:41:36 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2004-06-11 21:13:05 +00:00
|
|
|
|
|
|
|
|
if( bHaveRandom )
|
2004-11-05 08:54:09 +00:00
|
|
|
m_TrailCache.clear();
|
2004-06-11 21:13:05 +00:00
|
|
|
|
2005-03-11 18:09:34 +00:00
|
|
|
m_iTrailCacheSeed = GAMESTATE->m_iStageSeed;
|
2004-06-11 21:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
//
|
|
|
|
|
// Look in the Trail cache
|
|
|
|
|
//
|
2004-06-03 21:45:24 +00:00
|
|
|
{
|
2005-03-07 22:30:39 +00:00
|
|
|
TrailCache_t::iterator it = m_TrailCache.find( CacheEntry(st, cd) );
|
|
|
|
|
if( it != m_TrailCache.end() )
|
|
|
|
|
{
|
|
|
|
|
CacheData &cache = it->second;
|
|
|
|
|
if( cache.null )
|
|
|
|
|
return NULL;
|
|
|
|
|
return &cache.trail;
|
|
|
|
|
}
|
2004-06-03 21:45:24 +00:00
|
|
|
}
|
2003-02-14 21:42:44 +00:00
|
|
|
|
2004-05-25 02:17:17 +00:00
|
|
|
//
|
|
|
|
|
// Construct a new Trail, add it to the cache, then return it.
|
|
|
|
|
//
|
2004-11-05 08:54:09 +00:00
|
|
|
CacheData &cache = m_TrailCache[ CacheEntry(st, cd) ];
|
|
|
|
|
Trail &trail = cache.trail;
|
2004-06-02 07:15:19 +00:00
|
|
|
trail = Trail();
|
2004-06-03 21:45:24 +00:00
|
|
|
if( !GetTrailSorted( st, cd, trail ) || trail.m_vEntries.empty() )
|
|
|
|
|
{
|
|
|
|
|
/* This course difficulty doesn't exist. */
|
2004-11-05 08:54:09 +00:00
|
|
|
cache.null = true;
|
2004-06-03 21:45:24 +00:00
|
|
|
return NULL;
|
|
|
|
|
}
|
2004-06-03 08:22:02 +00:00
|
|
|
|
2005-03-07 22:30:39 +00:00
|
|
|
//
|
|
|
|
|
// If we have cached RadarValues for this trail, insert them.
|
|
|
|
|
//
|
|
|
|
|
{
|
|
|
|
|
RadarCache_t::const_iterator it = m_RadarCache.find( CacheEntry( st, cd ) );
|
|
|
|
|
if( it != m_RadarCache.end() )
|
|
|
|
|
{
|
|
|
|
|
const RadarValues &rv = it->second;
|
|
|
|
|
trail.SetRadarValues(rv);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-05 08:54:09 +00:00
|
|
|
cache.null = false;
|
|
|
|
|
return &cache.trail;
|
2004-06-03 08:22:02 +00:00
|
|
|
}
|
|
|
|
|
|
2004-06-03 21:45:24 +00:00
|
|
|
bool Course::GetTrailSorted( StepsType st, CourseDifficulty cd, Trail &trail ) const
|
2004-06-03 08:22:02 +00:00
|
|
|
{
|
2004-06-03 21:45:24 +00:00
|
|
|
if( !GetTrailUnsorted( st, cd, trail ) )
|
|
|
|
|
return false;
|
2004-05-25 02:17:17 +00:00
|
|
|
|
|
|
|
|
if( this->m_bSortByMeter )
|
|
|
|
|
{
|
2004-06-04 02:05:56 +00:00
|
|
|
/* Sort according to DIFFICULTY_MEDIUM, since the order of songs
|
2004-05-25 02:17:17 +00:00
|
|
|
* must not change across difficulties. */
|
|
|
|
|
Trail SortTrail;
|
2004-06-04 02:05:56 +00:00
|
|
|
if( cd == DIFFICULTY_MEDIUM )
|
2004-05-25 02:17:17 +00:00
|
|
|
SortTrail = trail;
|
|
|
|
|
else
|
2004-06-03 21:45:24 +00:00
|
|
|
{
|
2004-06-04 02:05:56 +00:00
|
|
|
bool bOK = GetTrailUnsorted( st, DIFFICULTY_MEDIUM, SortTrail );
|
2004-06-03 21:45:24 +00:00
|
|
|
|
2004-06-04 02:05:56 +00:00
|
|
|
/* If we have any other difficulty, we must have DIFFICULTY_MEDIUM. */
|
2004-06-03 21:45:24 +00:00
|
|
|
ASSERT( bOK );
|
|
|
|
|
}
|
2004-05-25 10:22:54 +00:00
|
|
|
ASSERT_M( trail.m_vEntries.size() == SortTrail.m_vEntries.size(),
|
|
|
|
|
ssprintf("%i %i", int(trail.m_vEntries.size()), int(SortTrail.m_vEntries.size())) );
|
2004-05-25 02:17:17 +00:00
|
|
|
|
|
|
|
|
vector<SortTrailEntry> entries;
|
|
|
|
|
for( unsigned i = 0; i < trail.m_vEntries.size(); ++i )
|
|
|
|
|
{
|
|
|
|
|
SortTrailEntry ste;
|
|
|
|
|
ste.entry = trail.m_vEntries[i];
|
|
|
|
|
ste.SortMeter = SortTrail.m_vEntries[i].pSteps->GetMeter();
|
|
|
|
|
entries.push_back( ste );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stable_sort( entries.begin(), entries.end() );
|
|
|
|
|
for( unsigned i = 0; i < trail.m_vEntries.size(); ++i )
|
|
|
|
|
trail.m_vEntries[i] = entries[i].entry;
|
|
|
|
|
}
|
2004-06-03 21:45:24 +00:00
|
|
|
|
|
|
|
|
return true;
|
2004-05-25 02:17:17 +00:00
|
|
|
}
|
|
|
|
|
|
2004-06-03 21:45:24 +00:00
|
|
|
bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const
|
2004-05-25 02:17:17 +00:00
|
|
|
{
|
2004-06-03 08:22:02 +00:00
|
|
|
trail.Init();
|
|
|
|
|
|
2004-06-04 02:05:56 +00:00
|
|
|
switch( cd )
|
|
|
|
|
{
|
|
|
|
|
case DIFFICULTY_BEGINNER:
|
|
|
|
|
case DIFFICULTY_CHALLENGE:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
//
|
|
|
|
|
// Construct a new Trail, add it to the cache, then return it.
|
|
|
|
|
//
|
2003-08-03 07:12:33 +00:00
|
|
|
/* Different seed for each course, but the same for the whole round: */
|
2005-03-11 18:09:34 +00:00
|
|
|
RandomGen rnd( GAMESTATE->m_iStageSeed + GetHashForString(m_sMainTitle) );
|
2003-08-03 07:12:33 +00:00
|
|
|
|
2003-12-21 02:54:23 +00:00
|
|
|
vector<CourseEntry> tmp_entries;
|
2003-02-14 21:42:44 +00:00
|
|
|
if( m_bRandomize )
|
2003-08-03 07:12:33 +00:00
|
|
|
{
|
|
|
|
|
/* Always randomize the same way per round. Otherwise, the displayed course
|
|
|
|
|
* will change every time it's viewed, and the displayed order will have no
|
|
|
|
|
* bearing on what you'll actually play. */
|
2005-06-28 08:11:30 +00:00
|
|
|
tmp_entries = m_vEntries;
|
2003-12-21 02:54:23 +00:00
|
|
|
random_shuffle( tmp_entries.begin(), tmp_entries.end(), rnd );
|
2003-08-03 07:12:33 +00:00
|
|
|
}
|
2003-02-05 20:00:49 +00:00
|
|
|
|
2005-06-28 08:11:30 +00:00
|
|
|
const vector<CourseEntry> &entries = m_bRandomize? tmp_entries:m_vEntries;
|
2003-12-21 02:54:23 +00:00
|
|
|
|
2003-08-04 21:34:45 +00:00
|
|
|
/* This can take some time, so don't fill it out unless we need it. */
|
|
|
|
|
vector<Song*> vSongsByMostPlayed;
|
2003-12-21 02:54:23 +00:00
|
|
|
vector<Song*> AllSongsShuffled;
|
2003-02-24 00:09:34 +00:00
|
|
|
|
2004-05-24 03:32:56 +00:00
|
|
|
trail.m_StepsType = st;
|
2004-05-23 09:17:10 +00:00
|
|
|
trail.m_CourseDifficulty = cd;
|
2003-07-21 21:54:07 +00:00
|
|
|
|
2004-06-03 21:45:24 +00:00
|
|
|
/* Set to true if CourseDifficulty is able to change something. */
|
2004-06-04 02:05:56 +00:00
|
|
|
bool bCourseDifficultyIsSignificant = (cd == DIFFICULTY_MEDIUM);
|
2005-06-27 04:41:36 +00:00
|
|
|
|
|
|
|
|
// Resolve each entry to a Song and Steps.
|
|
|
|
|
FOREACH_CONST( CourseEntry, entries, e )
|
2003-02-05 20:00:49 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
Song* pResolvedSong = NULL; // fill this in
|
|
|
|
|
Steps* pResolvedSteps = NULL; // fill this in
|
2003-02-05 20:00:49 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
//
|
|
|
|
|
// Create a list of matching songs.
|
|
|
|
|
//
|
2003-02-05 20:00:49 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
// Start with all songs
|
|
|
|
|
vector<Song*> vpPossibleSongs;
|
|
|
|
|
|
|
|
|
|
if( e->pSong )
|
|
|
|
|
{
|
|
|
|
|
// Choose an exact song
|
|
|
|
|
vpPossibleSongs.push_back( e->pSong );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
vpPossibleSongs = SONGMAN->GetAllSongs();
|
|
|
|
|
|
|
|
|
|
FOREACH( Song*, vpPossibleSongs, song )
|
|
|
|
|
{
|
|
|
|
|
// Ignore locked songs when choosing randomly
|
|
|
|
|
// TODO: Move Course initialization after UNLOCKMAN is created
|
|
|
|
|
if( UNLOCKMAN && UNLOCKMAN->SongIsLocked(*song) )
|
|
|
|
|
{
|
|
|
|
|
vector<Song*>::iterator eraseme = song;
|
|
|
|
|
song--;
|
|
|
|
|
vpPossibleSongs.erase( eraseme );
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ignore boring tutorial songs
|
|
|
|
|
if( (*song)->IsTutorial() )
|
|
|
|
|
{
|
|
|
|
|
vector<Song*>::iterator eraseme = song;
|
|
|
|
|
song--;
|
|
|
|
|
vpPossibleSongs.erase( eraseme );
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2002-07-23 01:41:40 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
// Don't allow long songs
|
|
|
|
|
if( SONGMAN->GetNumStagesForSong(*song) > 1 )
|
|
|
|
|
{
|
|
|
|
|
vector<Song*>::iterator eraseme = song;
|
|
|
|
|
song--;
|
|
|
|
|
vpPossibleSongs.erase( eraseme );
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Filter out all songs that don't have matching steps
|
|
|
|
|
// At the same time, create a list of matching song/steps.
|
|
|
|
|
typedef vector<Steps*> StepsVector;
|
|
|
|
|
map<Song*,StepsVector> mapSongToSteps;
|
|
|
|
|
FOREACH( Song*, vpPossibleSongs, song )
|
2003-02-16 10:12:03 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
// Apply song group filter
|
|
|
|
|
if( !e->sSongGroup.empty() && (*song)->m_sGroupName != e->sSongGroup )
|
2003-02-16 23:54:30 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
vector<Song*>::iterator eraseme = song;
|
|
|
|
|
song--;
|
|
|
|
|
vpPossibleSongs.erase( eraseme );
|
|
|
|
|
continue;
|
2003-02-16 23:54:30 +00:00
|
|
|
}
|
2005-06-27 04:41:36 +00:00
|
|
|
|
|
|
|
|
vector<Steps*> vpMatchingSteps;
|
|
|
|
|
(*song)->GetSteps( vpMatchingSteps, st );
|
|
|
|
|
|
|
|
|
|
FOREACH( Steps*, vpMatchingSteps, steps )
|
2003-02-16 10:12:03 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
if( e->baseDifficulty != DIFFICULTY_INVALID )
|
2003-12-21 02:54:23 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
Difficulty dc = e->baseDifficulty;
|
|
|
|
|
if( (*steps)->GetDifficulty() != dc )
|
|
|
|
|
{
|
|
|
|
|
vector<Steps*>::iterator eraseme = steps;
|
|
|
|
|
steps--;
|
|
|
|
|
vpMatchingSteps.erase( eraseme );
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2003-12-21 02:54:23 +00:00
|
|
|
}
|
|
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
if( e->iLowMeter != -1 )
|
2003-02-17 02:45:30 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
if( (*steps)->GetMeter() < e->iLowMeter )
|
|
|
|
|
{
|
|
|
|
|
vector<Steps*>::iterator eraseme = steps;
|
|
|
|
|
steps--;
|
|
|
|
|
vpMatchingSteps.erase( eraseme );
|
2005-03-27 11:52:04 +00:00
|
|
|
continue;
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
2005-03-27 11:52:04 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
if( e->iHighMeter != -1 )
|
|
|
|
|
{
|
|
|
|
|
if( (*steps)->GetMeter() > e->iHighMeter )
|
|
|
|
|
{
|
|
|
|
|
vector<Steps*>::iterator eraseme = steps;
|
|
|
|
|
steps--;
|
|
|
|
|
vpMatchingSteps.erase( eraseme );
|
2005-03-27 11:52:04 +00:00
|
|
|
continue;
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-03-27 11:52:04 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
if( vpMatchingSteps.empty() )
|
|
|
|
|
{
|
|
|
|
|
vector<Song*>::iterator eraseme = song;
|
|
|
|
|
song--;
|
|
|
|
|
vpPossibleSongs.erase( eraseme );
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2003-02-16 10:12:03 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
mapSongToSteps[*song] = vpMatchingSteps;
|
|
|
|
|
}
|
2003-02-24 00:09:34 +00:00
|
|
|
|
|
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
// TODO: Move Course initialization after PROFILEMAN is created
|
2005-06-28 08:11:30 +00:00
|
|
|
switch( e->songSort )
|
2005-06-27 04:41:36 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
default:
|
|
|
|
|
ASSERT(0);
|
|
|
|
|
case SongSort_Randomize:
|
|
|
|
|
random_shuffle( vpPossibleSongs.begin(), vpPossibleSongs.end(), rnd );
|
|
|
|
|
break;
|
|
|
|
|
case SongSort_MostPlays:
|
|
|
|
|
if( PROFILEMAN )
|
|
|
|
|
SongUtil::SortSongPointerArrayByNumPlays( vpPossibleSongs, PROFILEMAN->GetMachineProfile(), true ); // descending
|
|
|
|
|
break;
|
|
|
|
|
case SongSort_FewestPlays:
|
|
|
|
|
if( PROFILEMAN )
|
|
|
|
|
SongUtil::SortSongPointerArrayByNumPlays( vpPossibleSongs, PROFILEMAN->GetMachineProfile(), false ); // ascending
|
|
|
|
|
break;
|
|
|
|
|
case SongSort_TopGrades:
|
|
|
|
|
SongUtil::SortSongPointerArrayByGrades( vpPossibleSongs, true ); // descending
|
|
|
|
|
break;
|
|
|
|
|
case SongSort_LowestGrades:
|
|
|
|
|
SongUtil::SortSongPointerArrayByGrades( vpPossibleSongs, false ); // ascending
|
|
|
|
|
break;
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
2005-06-28 08:11:30 +00:00
|
|
|
|
2005-06-29 06:36:51 +00:00
|
|
|
if( e->iChooseIndex < int(vpPossibleSongs.size()) )
|
2005-06-27 04:41:36 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
pResolvedSong = vpPossibleSongs[e->iChooseIndex];
|
|
|
|
|
vector<Steps*> &vpPossibleSteps = mapSongToSteps[pResolvedSong];
|
|
|
|
|
ASSERT( !vpPossibleSteps.empty() ); // if no steps are playable, this shouldn't be a possible song
|
|
|
|
|
random_shuffle( vpPossibleSteps.begin(), vpPossibleSteps.end(), rnd );
|
|
|
|
|
pResolvedSteps = vpPossibleSteps[0];
|
2003-02-16 10:12:03 +00:00
|
|
|
}
|
2002-12-17 05:07:56 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
|
|
|
|
|
if( pResolvedSong == NULL || pResolvedSteps == NULL )
|
2003-02-16 10:12:03 +00:00
|
|
|
continue; // this song entry isn't playable. Skip.
|
2002-12-17 05:07:56 +00:00
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
|
|
|
|
|
/* If we're not COURSE_DIFFICULTY_REGULAR, then we should be choosing steps that are
|
|
|
|
|
* either easier or harder than the base difficulty. If no such steps exist, then
|
|
|
|
|
* just use the one we already have. */
|
|
|
|
|
Difficulty dc = pResolvedSteps->GetDifficulty();
|
|
|
|
|
int iLowMeter = e->iLowMeter;
|
|
|
|
|
int iHighMeter = e->iHighMeter;
|
|
|
|
|
if( cd != DIFFICULTY_MEDIUM && !e->bNoDifficult )
|
2003-03-28 02:25:15 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
Difficulty new_dc = (Difficulty)(dc + cd - DIFFICULTY_MEDIUM);
|
|
|
|
|
new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(DIFFICULTY_EDIT-1) );
|
2004-05-31 21:05:34 +00:00
|
|
|
|
2004-05-25 20:41:41 +00:00
|
|
|
bool bChangedDifficulty = false;
|
2004-05-31 21:05:34 +00:00
|
|
|
if( new_dc != dc )
|
2003-03-28 02:25:15 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
Steps* pNewSteps = pResolvedSong->GetStepsByDifficulty( st, new_dc );
|
2004-05-31 21:05:34 +00:00
|
|
|
if( pNewSteps )
|
2004-05-25 20:41:41 +00:00
|
|
|
{
|
2004-05-31 21:05:34 +00:00
|
|
|
dc = new_dc;
|
2005-06-27 04:41:36 +00:00
|
|
|
pResolvedSteps = pNewSteps;
|
2004-05-25 20:41:41 +00:00
|
|
|
bChangedDifficulty = true;
|
2004-06-03 21:45:24 +00:00
|
|
|
bCourseDifficultyIsSignificant = true;
|
2004-05-25 20:41:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Hack: We used to adjust low_meter/high_meter above while searching for
|
|
|
|
|
* songs. However, that results in a different song being chosen for
|
|
|
|
|
* difficult courses, which is bad when LockCourseDifficulties is disabled;
|
|
|
|
|
* each player can end up with a different song. Instead, choose based
|
|
|
|
|
* on the original range, bump the steps based on course difficulty, and
|
|
|
|
|
* then retroactively tweak the low_meter/high_meter so course displays
|
|
|
|
|
* line up. */
|
2005-06-27 04:41:36 +00:00
|
|
|
if( e->baseDifficulty == DIFFICULTY_INVALID && bChangedDifficulty )
|
2004-05-25 20:41:41 +00:00
|
|
|
{
|
|
|
|
|
/* Minimum and maximum to add to make the meter range contain the actual
|
|
|
|
|
* meter: */
|
2005-06-27 04:41:36 +00:00
|
|
|
int iMinDist = pResolvedSteps->GetMeter() - iHighMeter;
|
|
|
|
|
int iMaxDist = pResolvedSteps->GetMeter() - iLowMeter;
|
2004-05-25 20:41:41 +00:00
|
|
|
|
|
|
|
|
/* Clamp the possible adjustments to try to avoid going under 1 or over
|
|
|
|
|
* MAX_BOTTOM_RANGE. */
|
2005-06-27 04:41:36 +00:00
|
|
|
iMinDist = min( max( iMinDist, -iLowMeter+1 ), iMaxDist );
|
|
|
|
|
iMaxDist = max( min( iMaxDist, MAX_BOTTOM_RANGE-iHighMeter ), iMinDist );
|
2004-05-25 20:41:41 +00:00
|
|
|
|
|
|
|
|
int iAdd;
|
|
|
|
|
if( iMaxDist == iMinDist )
|
|
|
|
|
iAdd = iMaxDist;
|
|
|
|
|
else
|
|
|
|
|
iAdd = rnd(iMaxDist-iMinDist) + iMinDist;
|
2005-06-27 04:41:36 +00:00
|
|
|
iLowMeter += iAdd;
|
|
|
|
|
iHighMeter += iAdd;
|
2003-03-28 02:25:15 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
TrailEntry te;
|
2005-06-27 04:41:36 +00:00
|
|
|
te.pSong = pResolvedSong;
|
|
|
|
|
te.pSteps = pResolvedSteps;
|
|
|
|
|
te.Modifiers = e->sModifiers;
|
|
|
|
|
te.Attacks = e->attacks;
|
|
|
|
|
te.bSecret = e->bSecret;
|
|
|
|
|
te.iLowMeter = iLowMeter;
|
|
|
|
|
te.iHighMeter = iHighMeter;
|
|
|
|
|
|
2004-06-08 07:19:56 +00:00
|
|
|
/* If we chose based on meter (not difficulty), then store DIFFICULTY_INVALID, so
|
|
|
|
|
* other classes can tell that we used meter. */
|
2005-06-27 04:41:36 +00:00
|
|
|
if( e->baseDifficulty == DIFFICULTY_INVALID )
|
|
|
|
|
{
|
2004-06-08 07:19:56 +00:00
|
|
|
te.dc = DIFFICULTY_INVALID;
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
2004-06-08 07:19:56 +00:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Otherwise, store the actual difficulty we got (post-course-difficulty).
|
|
|
|
|
* This may or may not be the same as e.difficulty. */
|
|
|
|
|
te.dc = dc;
|
|
|
|
|
}
|
2004-05-23 00:53:20 +00:00
|
|
|
trail.m_vEntries.push_back( te );
|
2003-02-14 21:42:44 +00:00
|
|
|
}
|
2004-06-02 08:13:09 +00:00
|
|
|
|
2004-08-05 20:03:03 +00:00
|
|
|
/* Hack: If any entry was non-FIXED, or m_bRandomize is set, then radar values
|
|
|
|
|
* for this trail will be meaningless as they'll change every time. Pre-cache
|
|
|
|
|
* empty data. XXX: How can we do this cleanly, without propagating lots of
|
|
|
|
|
* otherwise unnecessary data (course entry types, m_bRandomize) to Trail, or
|
|
|
|
|
* storing a Course pointer in Trail (yuck)? */
|
|
|
|
|
if( !AllSongsAreFixed() || m_bRandomize )
|
|
|
|
|
{
|
|
|
|
|
trail.m_bRadarValuesCached = true;
|
|
|
|
|
trail.m_CachedRadarValues = RadarValues();
|
|
|
|
|
}
|
|
|
|
|
|
2004-06-02 08:13:09 +00:00
|
|
|
/* If we have a manually-entered meter for this difficulty, use it. */
|
|
|
|
|
if( m_iCustomMeter[cd] != -1 )
|
2004-06-03 08:22:02 +00:00
|
|
|
trail.m_iSpecifiedMeter = m_iCustomMeter[cd];
|
2004-06-03 21:45:24 +00:00
|
|
|
|
|
|
|
|
/* If the course difficulty never actually changed anything, then this difficulty
|
2004-06-04 02:05:56 +00:00
|
|
|
* is equivalent to DIFFICULTY_MEDIUM; it doesn't exist. */
|
2004-06-03 21:45:24 +00:00
|
|
|
return bCourseDifficultyIsSignificant;
|
2003-12-21 02:54:23 +00:00
|
|
|
}
|
|
|
|
|
|
2004-06-03 08:22:02 +00:00
|
|
|
void Course::GetTrails( vector<Trail*> &AddTo, StepsType st ) const
|
2004-06-02 07:15:19 +00:00
|
|
|
{
|
2004-06-04 02:52:07 +00:00
|
|
|
FOREACH_ShownCourseDifficulty( cd )
|
2004-06-02 07:15:19 +00:00
|
|
|
{
|
|
|
|
|
Trail *pTrail = GetTrail( st, cd );
|
|
|
|
|
if( pTrail == NULL )
|
|
|
|
|
continue;
|
2004-06-03 08:22:02 +00:00
|
|
|
AddTo.push_back( pTrail );
|
2004-06-02 07:15:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2005-03-22 10:33:47 +00:00
|
|
|
void Course::GetAllTrails( vector<Trail*> &AddTo ) const
|
|
|
|
|
{
|
|
|
|
|
vector<StepsType> vStepsTypesToShow;
|
|
|
|
|
GAMEMAN->GetStepsTypesForGame( GAMESTATE->m_pCurGame, vStepsTypesToShow );
|
|
|
|
|
FOREACH( StepsType, vStepsTypesToShow, st )
|
|
|
|
|
{
|
|
|
|
|
GetTrails( AddTo, *st );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-13 23:11:57 +00:00
|
|
|
bool Course::HasMods() const
|
|
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
FOREACH_CONST( CourseEntry, m_vEntries, e )
|
2004-03-13 23:11:57 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
if( !e->sModifiers.empty() || !e->attacks.empty() )
|
2004-03-13 23:11:57 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-20 19:24:38 +00:00
|
|
|
bool Course::AllSongsAreFixed() const
|
|
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
FOREACH_CONST( CourseEntry, m_vEntries, e )
|
2004-03-20 19:24:38 +00:00
|
|
|
{
|
2005-06-27 04:41:36 +00:00
|
|
|
if( e->pSong == NULL )
|
2004-03-20 19:24:38 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2004-07-24 06:40:51 +00:00
|
|
|
void Course::Invalidate( Song *pStaleSong )
|
2003-12-21 02:54:23 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
FOREACH_CONST( CourseEntry, m_vEntries, e )
|
2004-07-24 06:40:51 +00:00
|
|
|
{
|
|
|
|
|
if( e->pSong == pStaleSong ) // a fixed entry that references the stale Song
|
|
|
|
|
{
|
|
|
|
|
RevertFromDisk();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Invalidate any Trails that contain this song.
|
|
|
|
|
// If we find a Trail that contains this song, then it's part of a
|
|
|
|
|
// non-fixed entry. So, regenerating the Trail will force different
|
|
|
|
|
// songs to be chosen.
|
|
|
|
|
FOREACH_StepsType( st )
|
|
|
|
|
FOREACH_ShownCourseDifficulty( cd )
|
2004-11-05 08:54:09 +00:00
|
|
|
{
|
|
|
|
|
TrailCache_t::iterator it = m_TrailCache.find( CacheEntry(st, cd) );
|
|
|
|
|
if( it == m_TrailCache.end() )
|
|
|
|
|
continue;
|
|
|
|
|
CacheData &cache = it->second;
|
|
|
|
|
if( !cache.null )
|
2004-07-24 06:40:51 +00:00
|
|
|
if( GetTrail( st, cd )->ContainsSong( pStaleSong ) )
|
2004-11-05 08:54:09 +00:00
|
|
|
m_TrailCache.erase( it );
|
|
|
|
|
}
|
2004-07-24 06:40:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Course::RegenerateNonFixedTrails()
|
|
|
|
|
{
|
|
|
|
|
// Only need to regen Trails if the Course has a random entry.
|
|
|
|
|
// We can create these Trails on demand because we don't
|
|
|
|
|
// calculate RadarValues for Trails with one or more non-fixed
|
|
|
|
|
// entry.
|
|
|
|
|
if( !IsFixed() )
|
2004-11-05 08:54:09 +00:00
|
|
|
m_TrailCache.clear();
|
2002-07-27 19:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
2003-02-14 21:42:44 +00:00
|
|
|
RageColor Course::GetColor() const
|
2002-07-27 19:29:51 +00:00
|
|
|
{
|
2004-10-07 22:20:52 +00:00
|
|
|
// FIXME: Calculate the meter.
|
2004-05-23 00:53:20 +00:00
|
|
|
int iMeter = 5;
|
|
|
|
|
|
2005-05-19 20:34:35 +00:00
|
|
|
switch( PREFSMAN->m_CourseSortOrder )
|
2003-07-27 14:54:56 +00:00
|
|
|
{
|
2003-07-27 19:14:05 +00:00
|
|
|
case PrefsManager::COURSE_SORT_SONGS:
|
2005-06-28 08:11:30 +00:00
|
|
|
if( m_vEntries.size() >= 7 ) return SORT_LEVEL2_COLOR;
|
|
|
|
|
else if( m_vEntries.size() >= 4 ) return SORT_LEVEL4_COLOR;
|
2004-10-07 22:20:52 +00:00
|
|
|
else return SORT_LEVEL5_COLOR;
|
2003-07-27 14:54:56 +00:00
|
|
|
|
2003-07-27 19:14:05 +00:00
|
|
|
case PrefsManager::COURSE_SORT_METER:
|
2005-05-19 20:34:35 +00:00
|
|
|
if( !IsFixed() ) return SORT_LEVEL1_COLOR;
|
|
|
|
|
else if( iMeter > 9 ) return SORT_LEVEL2_COLOR;
|
|
|
|
|
else if( iMeter >= 7 ) return SORT_LEVEL3_COLOR;
|
|
|
|
|
else if( iMeter >= 5 ) return SORT_LEVEL4_COLOR;
|
2004-10-07 22:20:52 +00:00
|
|
|
else return SORT_LEVEL5_COLOR;
|
2003-07-27 14:54:56 +00:00
|
|
|
|
2003-07-27 19:14:05 +00:00
|
|
|
case PrefsManager::COURSE_SORT_METER_SUM:
|
2005-05-19 20:34:35 +00:00
|
|
|
if( !IsFixed() ) return SORT_LEVEL1_COLOR;
|
|
|
|
|
if( m_SortOrder_TotalDifficulty >= 40 ) return SORT_LEVEL2_COLOR;
|
|
|
|
|
if( m_SortOrder_TotalDifficulty >= 30 ) return SORT_LEVEL3_COLOR;
|
|
|
|
|
if( m_SortOrder_TotalDifficulty >= 20 ) return SORT_LEVEL4_COLOR;
|
2004-10-07 22:20:52 +00:00
|
|
|
else return SORT_LEVEL5_COLOR;
|
2003-07-27 14:54:56 +00:00
|
|
|
|
2003-07-27 19:14:05 +00:00
|
|
|
case PrefsManager::COURSE_SORT_RANK:
|
2005-05-19 20:34:35 +00:00
|
|
|
if( m_SortOrder_Ranking == 3 ) return SORT_LEVEL1_COLOR;
|
|
|
|
|
else if( m_SortOrder_Ranking == 2 ) return SORT_LEVEL3_COLOR;
|
|
|
|
|
else if( m_SortOrder_Ranking == 1 ) return SORT_LEVEL5_COLOR;
|
2004-10-07 22:20:52 +00:00
|
|
|
else return SORT_LEVEL4_COLOR;
|
2003-07-27 19:14:05 +00:00
|
|
|
default:
|
|
|
|
|
ASSERT(0);
|
|
|
|
|
return RageColor(1,1,1,1); // white, never should reach here
|
2003-07-27 14:54:56 +00:00
|
|
|
}
|
2002-07-27 19:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
2003-08-12 20:28:56 +00:00
|
|
|
bool Course::IsFixed() const
|
|
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
for(unsigned i = 0; i < m_vEntries.size(); i++)
|
2003-08-12 20:28:56 +00:00
|
|
|
{
|
2005-07-06 21:46:55 +00:00
|
|
|
if( m_vEntries[i].pSong == NULL )
|
|
|
|
|
return false;
|
2003-08-12 20:28:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2003-02-16 10:12:03 +00:00
|
|
|
|
2003-07-21 22:21:03 +00:00
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
bool Course::GetTotalSeconds( StepsType st, float& fSecondsOut ) const
|
2003-07-21 22:21:03 +00:00
|
|
|
{
|
2004-05-23 00:53:20 +00:00
|
|
|
if( !IsFixed() )
|
|
|
|
|
return false;
|
2003-07-21 22:21:03 +00:00
|
|
|
|
2004-06-04 02:05:56 +00:00
|
|
|
Trail* pTrail = GetTrail( st, DIFFICULTY_MEDIUM );
|
2003-02-14 21:42:44 +00:00
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
fSecondsOut = pTrail->GetLengthSeconds();
|
2003-02-14 21:42:44 +00:00
|
|
|
return true;
|
2002-12-17 05:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
2003-08-12 00:18:33 +00:00
|
|
|
bool Course::CourseHasBestOrWorst() const
|
|
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
FOREACH_CONST( CourseEntry, m_vEntries, e )
|
2003-08-12 00:18:33 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
if( e->iChooseIndex == SongSort_MostPlays && e->iChooseIndex != -1 )
|
2005-06-27 04:41:36 +00:00
|
|
|
return true;
|
2005-06-28 08:11:30 +00:00
|
|
|
if( e->iChooseIndex == SongSort_FewestPlays && e->iChooseIndex != -1 )
|
2003-08-12 00:18:33 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2003-04-19 18:51:13 +00:00
|
|
|
bool Course::HasBanner() const
|
|
|
|
|
{
|
|
|
|
|
return m_sBannerPath != "" && IsAFile(m_sBannerPath);
|
|
|
|
|
}
|
2003-07-27 14:10:47 +00:00
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
void Course::UpdateCourseStats( StepsType st )
|
2003-07-27 14:10:47 +00:00
|
|
|
{
|
2004-05-23 00:53:20 +00:00
|
|
|
m_SortOrder_TotalDifficulty = 0;
|
2003-07-27 14:10:47 +00:00
|
|
|
|
2003-08-04 20:45:34 +00:00
|
|
|
// courses with random/players best-worst songs should go at the end
|
2005-06-28 08:11:30 +00:00
|
|
|
for(unsigned i = 0; i < m_vEntries.size(); i++)
|
2003-08-04 20:45:34 +00:00
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
if ( m_vEntries[i].pSong != NULL )
|
2003-08-04 20:45:34 +00:00
|
|
|
continue;
|
|
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
if ( m_SortOrder_Ranking == 2 )
|
|
|
|
|
m_SortOrder_Ranking = 3;
|
2005-03-25 12:37:17 +00:00
|
|
|
m_SortOrder_TotalDifficulty = INT_MAX;
|
2003-08-04 20:45:34 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2004-06-20 16:54:51 +00:00
|
|
|
const Trail* pTrail = GetTrail( st, DIFFICULTY_MEDIUM );
|
2003-07-27 14:10:47 +00:00
|
|
|
|
2004-06-20 16:54:51 +00:00
|
|
|
m_SortOrder_TotalDifficulty += pTrail != NULL? pTrail->GetTotalMeter():0;
|
2003-07-27 14:10:47 +00:00
|
|
|
|
2003-08-04 09:18:26 +00:00
|
|
|
// OPTIMIZATION: Ranking info isn't dependant on style, so
|
|
|
|
|
// call it sparingly. Its handled on startup and when
|
|
|
|
|
// themes change..
|
2003-08-09 22:08:17 +00:00
|
|
|
|
2003-09-19 01:16:42 +00:00
|
|
|
LOG->Trace("%s: Total feet: %d",
|
2004-07-11 10:02:38 +00:00
|
|
|
this->m_sMainTitle.c_str(),
|
2004-05-23 00:53:20 +00:00
|
|
|
m_SortOrder_TotalDifficulty );
|
2003-10-25 22:55:11 +00:00
|
|
|
}
|
2003-11-03 10:14:13 +00:00
|
|
|
|
|
|
|
|
bool Course::IsRanking() const
|
|
|
|
|
{
|
|
|
|
|
CStringArray rankingsongs;
|
|
|
|
|
|
|
|
|
|
split(THEME->GetMetric("ScreenRanking", "CoursesToShow"), ",", rankingsongs);
|
|
|
|
|
|
|
|
|
|
for(unsigned i=0; i < rankingsongs.size(); i++)
|
|
|
|
|
if (rankingsongs[i].CompareNoCase(m_sPath))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
2003-11-05 05:00:32 +00:00
|
|
|
}
|
2003-11-09 22:16:59 +00:00
|
|
|
|
2005-01-03 23:30:56 +00:00
|
|
|
const CourseEntry *Course::FindFixedSong( const Song *pSong ) const
|
|
|
|
|
{
|
2005-06-28 08:11:30 +00:00
|
|
|
FOREACH_CONST( CourseEntry, m_vEntries, e )
|
2005-01-03 23:30:56 +00:00
|
|
|
{
|
2005-06-27 05:41:43 +00:00
|
|
|
const CourseEntry &entry = *e;
|
|
|
|
|
if( entry.pSong == pSong )
|
|
|
|
|
return &entry;
|
2005-01-03 23:30:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2004-08-30 22:15:57 +00:00
|
|
|
void Course::GetAllCachedTrails( vector<Trail *> &out )
|
|
|
|
|
{
|
2004-11-05 08:54:09 +00:00
|
|
|
TrailCache_t::iterator it;
|
|
|
|
|
for( it = m_TrailCache.begin(); it != m_TrailCache.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
CacheData &cd = it->second;
|
|
|
|
|
if( !cd.null )
|
|
|
|
|
out.push_back( &cd.trail );
|
|
|
|
|
}
|
2004-08-30 22:15:57 +00:00
|
|
|
}
|
|
|
|
|
|
2005-03-27 10:31:27 +00:00
|
|
|
bool Course::ShowInDemonstrationAndRanking() const
|
|
|
|
|
{
|
|
|
|
|
// Don't show endless courses in Ranking.
|
|
|
|
|
if( IsEndless() )
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2005-03-27 11:52:04 +00:00
|
|
|
void Course::CalculateRadarValues()
|
|
|
|
|
{
|
|
|
|
|
FOREACH_StepsType( st )
|
|
|
|
|
{
|
|
|
|
|
FOREACH_CourseDifficulty( cd )
|
|
|
|
|
{
|
|
|
|
|
// For courses that aren't fixed, the radar values are meaningless.
|
|
|
|
|
// Makes non-fixed courses have unknown radar values.
|
|
|
|
|
if( IsFixed() )
|
|
|
|
|
{
|
|
|
|
|
Trail *pTrail = GetTrail( st, cd );
|
|
|
|
|
if( pTrail == NULL )
|
|
|
|
|
continue;
|
|
|
|
|
RadarValues rv = pTrail->GetRadarValues();
|
|
|
|
|
m_RadarCache[CacheEntry(st, cd)] = rv;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
m_RadarCache[CacheEntry(st, cd)] = RadarValues();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-02-21 17:20:11 +00:00
|
|
|
|
|
|
|
|
// lua start
|
|
|
|
|
#include "LuaBinding.h"
|
|
|
|
|
|
2005-06-20 05:02:03 +00:00
|
|
|
class LunaCourse: public Luna<Course>
|
2005-02-21 17:20:11 +00:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
LunaCourse() { LUA->Register( Register ); }
|
|
|
|
|
|
|
|
|
|
static int GetPlayMode( T* p, lua_State *L ) { lua_pushnumber(L, p->GetPlayMode() ); return 1; }
|
2005-05-23 00:38:09 +00:00
|
|
|
static int GetDisplayFullTitle( T* p, lua_State *L ) { lua_pushstring(L, p->GetDisplayFullTitle() ); return 1; }
|
|
|
|
|
static int GetTranslitFullTitle( T* p, lua_State *L ) { lua_pushstring(L, p->GetTranslitFullTitle() ); return 1; }
|
2005-07-29 23:02:10 +00:00
|
|
|
static int HasMods( T* p, lua_State *L ) { lua_pushboolean(L, p->HasMods() ); return 1; }
|
2005-02-21 17:20:11 +00:00
|
|
|
|
|
|
|
|
static void Register(lua_State *L)
|
|
|
|
|
{
|
|
|
|
|
ADD_METHOD( GetPlayMode )
|
2005-05-23 00:38:09 +00:00
|
|
|
ADD_METHOD( GetDisplayFullTitle )
|
|
|
|
|
ADD_METHOD( GetTranslitFullTitle )
|
2005-07-29 23:02:10 +00:00
|
|
|
ADD_METHOD( HasMods )
|
2005-02-21 17:20:11 +00:00
|
|
|
Luna<T>::Register( L );
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
LUA_REGISTER_CLASS( Course )
|
|
|
|
|
// lua end
|
|
|
|
|
|
|
|
|
|
|
2004-05-31 22:42:12 +00:00
|
|
|
/*
|
|
|
|
|
* (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.
|
|
|
|
|
*/
|