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"
|
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-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-07-31 05:41:32 +00:00
|
|
|
#include "CourseLoaderCRS.h"
|
2005-08-01 05:18:24 +00:00
|
|
|
#include "LuaFunctions.h"
|
2006-01-07 04:11:29 +00:00
|
|
|
#include "LocalizedString.h"
|
2006-03-27 21:53:33 +00:00
|
|
|
#include "StepsUtil.h"
|
2005-06-26 21:31:07 +00:00
|
|
|
|
2006-01-04 22:30:51 +00:00
|
|
|
static const char *CourseTypeNames[] = {
|
2005-07-29 23:02:10 +00:00
|
|
|
"Nonstop",
|
|
|
|
|
"Oni",
|
|
|
|
|
"Endless",
|
|
|
|
|
"Survival",
|
|
|
|
|
};
|
|
|
|
|
XToString( CourseType, NUM_CourseType );
|
2006-01-07 04:11:29 +00:00
|
|
|
XToLocalizedString( CourseType );
|
2005-07-29 23:02:10 +00:00
|
|
|
|
2006-01-07 04:11:29 +00:00
|
|
|
LuaFunction( CourseTypeToLocalizedString, CourseTypeToLocalizedString((CourseType) IArg(1)) );
|
2005-08-01 05:18:24 +00:00
|
|
|
|
|
|
|
|
|
2006-01-04 22:30:51 +00:00
|
|
|
static const char *SongSortNames[] = {
|
2005-06-28 08:11:30 +00:00
|
|
|
"Randomize",
|
|
|
|
|
"MostPlays",
|
|
|
|
|
"FewestPlays",
|
|
|
|
|
"TopGrades",
|
|
|
|
|
"LowestGrades",
|
|
|
|
|
};
|
|
|
|
|
XToString( SongSort, NUM_SongSort );
|
2006-01-07 04:11:29 +00:00
|
|
|
XToLocalizedString( SongSort );
|
2005-06-28 08:11:30 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
2006-01-22 01:00:06 +00:00
|
|
|
RString CourseEntry::GetTextDescription() const
|
2005-07-30 04:50:17 +00:00
|
|
|
{
|
2006-01-22 01:00:06 +00:00
|
|
|
vector<RString> vsEntryDescription;
|
2005-07-30 04:50:17 +00:00
|
|
|
if( pSong )
|
|
|
|
|
vsEntryDescription.push_back( pSong->GetTranslitFullTitle() );
|
|
|
|
|
else
|
|
|
|
|
vsEntryDescription.push_back( "Random" );
|
|
|
|
|
if( !sSongGroup.empty() )
|
|
|
|
|
vsEntryDescription.push_back( sSongGroup );
|
2005-08-15 16:03:46 +00:00
|
|
|
if( baseDifficulty != DIFFICULTY_INVALID && baseDifficulty != DIFFICULTY_MEDIUM )
|
2006-01-07 04:11:29 +00:00
|
|
|
vsEntryDescription.push_back( DifficultyToLocalizedString(baseDifficulty) );
|
2005-07-30 04:50:17 +00:00
|
|
|
if( iLowMeter != -1 )
|
|
|
|
|
vsEntryDescription.push_back( ssprintf("Low meter: %d", iLowMeter) );
|
|
|
|
|
if( iHighMeter != -1 )
|
|
|
|
|
vsEntryDescription.push_back( ssprintf("High meter: %d", iHighMeter) );
|
|
|
|
|
if( songSort != SongSort_Randomize )
|
2006-01-07 04:11:29 +00:00
|
|
|
vsEntryDescription.push_back( "Sort: %d" + SongSortToLocalizedString(songSort) );
|
2005-07-30 04:50:17 +00:00
|
|
|
if( songSort != SongSort_Randomize && iChooseIndex != 0 )
|
|
|
|
|
vsEntryDescription.push_back( "Choose " + FormatNumberAndSuffix(iChooseIndex) + " match" );
|
2005-07-30 19:34:23 +00:00
|
|
|
int iNumModChanges = GetNumModChanges();
|
2005-07-30 04:50:17 +00:00
|
|
|
if( iNumModChanges != 0 )
|
|
|
|
|
vsEntryDescription.push_back( ssprintf("%d mod changes", iNumModChanges) );
|
|
|
|
|
if( fGainSeconds != 0 )
|
|
|
|
|
vsEntryDescription.push_back( ssprintf("Low meter: %.0f", fGainSeconds) );
|
|
|
|
|
|
2006-01-22 01:00:06 +00:00
|
|
|
RString s = join( ",", vsEntryDescription );
|
2005-07-30 04:50:17 +00:00
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2005-07-30 19:34:23 +00:00
|
|
|
int CourseEntry::GetNumModChanges() const
|
|
|
|
|
{
|
|
|
|
|
int iNumModChanges = 0;
|
|
|
|
|
if( !sModifiers.empty() )
|
|
|
|
|
iNumModChanges++;
|
|
|
|
|
iNumModChanges += attacks.size();
|
|
|
|
|
return iNumModChanges;
|
|
|
|
|
}
|
|
|
|
|
|
2005-08-03 03:12:09 +00:00
|
|
|
// lua start
|
|
|
|
|
#include "LuaBinding.h"
|
|
|
|
|
|
|
|
|
|
class LunaCourseEntry: public Luna<CourseEntry>
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
LunaCourseEntry() { LUA->Register( Register ); }
|
|
|
|
|
|
|
|
|
|
static int GetSong( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
if( p->pSong )
|
|
|
|
|
p->pSong->PushSelf(L);
|
|
|
|
|
else
|
|
|
|
|
lua_pushnil(L);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void Register(lua_State *L)
|
|
|
|
|
{
|
2005-09-10 02:47:04 +00:00
|
|
|
ADD_METHOD( GetSong );
|
2005-08-03 03:12:09 +00:00
|
|
|
|
|
|
|
|
Luna<T>::Register( L );
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
LUA_REGISTER_CLASS( CourseEntry )
|
|
|
|
|
// lua end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2005-07-30 04:50:17 +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-08-01 05:18:24 +00:00
|
|
|
if( !m_vEntries.empty() && m_vEntries[0].fGainSeconds > 0 )
|
|
|
|
|
return COURSE_TYPE_SURVIVAL;
|
2005-04-21 04:27:13 +00:00
|
|
|
return COURSE_TYPE_NONSTOP;
|
2005-04-07 08:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
2005-08-01 05:18:24 +00:00
|
|
|
void Course::SetCourseType( CourseType ct )
|
|
|
|
|
{
|
|
|
|
|
if( GetCourseType() == ct )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_bRepeat = false;
|
|
|
|
|
m_iLives = -1;
|
|
|
|
|
if( !m_vEntries.empty() )
|
|
|
|
|
m_vEntries[0].fGainSeconds = 0;
|
|
|
|
|
|
|
|
|
|
switch( ct )
|
|
|
|
|
{
|
|
|
|
|
default: ASSERT(0);
|
|
|
|
|
case COURSE_TYPE_NONSTOP:
|
|
|
|
|
break;
|
|
|
|
|
case COURSE_TYPE_ONI:
|
|
|
|
|
m_iLives = 4;
|
|
|
|
|
break;
|
|
|
|
|
case COURSE_TYPE_ENDLESS:
|
|
|
|
|
m_bRepeat = true;
|
|
|
|
|
break;
|
|
|
|
|
case COURSE_TYPE_SURVIVAL:
|
|
|
|
|
if( !m_vEntries.empty() )
|
|
|
|
|
m_vEntries[0].fGainSeconds = 120;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
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() );
|
|
|
|
|
|
2005-07-31 05:41:32 +00:00
|
|
|
CourseLoaderCRS::LoadFromCRSFile( m_sPath, *this );
|
2004-07-24 06:40:51 +00:00
|
|
|
}
|
|
|
|
|
|
2006-01-22 01:00:06 +00:00
|
|
|
RString Course::GetCacheFilePath() const
|
2005-03-07 22:30:39 +00:00
|
|
|
{
|
|
|
|
|
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;
|
2005-08-01 05:18:24 +00:00
|
|
|
m_bShuffle = false;
|
2004-01-26 20:55:35 +00:00
|
|
|
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-12-01 03:20:25 +00:00
|
|
|
m_LoadedFromProfile = ProfileSlot_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
|
|
|
|
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; }
|
|
|
|
|
};
|
|
|
|
|
|
2006-01-22 01:00:06 +00:00
|
|
|
RString 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;
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-22 01:00:06 +00:00
|
|
|
RString Course::GetDisplaySubTitle() const
|
2004-07-11 10:02:38 +00:00
|
|
|
{
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-22 01:00:06 +00:00
|
|
|
RString Course::GetDisplayFullTitle() const
|
2004-07-11 10:02:38 +00:00
|
|
|
{
|
2006-01-22 01:00:06 +00:00
|
|
|
RString Title = GetDisplayMainTitle();
|
|
|
|
|
RString SubTitle = GetDisplaySubTitle();
|
2004-07-11 10:02:38 +00:00
|
|
|
|
|
|
|
|
if(!SubTitle.empty()) Title += " " + SubTitle;
|
|
|
|
|
return Title;
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-22 01:00:06 +00:00
|
|
|
RString Course::GetTranslitFullTitle() const
|
2004-07-11 10:02:38 +00:00
|
|
|
{
|
2006-01-22 01:00:06 +00:00
|
|
|
RString Title = GetTranslitMainTitle();
|
|
|
|
|
RString SubTitle = GetTranslitSubTitle();
|
2004-07-11 10:02:38 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2006-02-21 01:48:01 +00:00
|
|
|
RegenerateNonFixedTrails();
|
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
|
|
|
|
2005-08-01 05:18:24 +00:00
|
|
|
return GetTrailForceRegenCache( st, cd );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Trail* Course::GetTrailForceRegenCache( StepsType st, CourseDifficulty cd ) const
|
|
|
|
|
{
|
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(),
|
2006-02-14 11:16:39 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2005-10-31 00:54:52 +00:00
|
|
|
// TODO: Move Course initialization after PROFILEMAN is created
|
|
|
|
|
static void CourseSortSongs( SongSort sort, vector<Song*> &vpPossibleSongs, RandomGen &rnd )
|
|
|
|
|
{
|
|
|
|
|
switch( sort )
|
|
|
|
|
{
|
|
|
|
|
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-10-31 01:19:54 +00:00
|
|
|
static void GetSongsValidForRandom( vector<Song*> &vpSongsOut )
|
|
|
|
|
{
|
|
|
|
|
const vector<Song*> &vpAllSongs = SONGMAN->GetAllSongs();
|
|
|
|
|
|
|
|
|
|
FOREACH_CONST( Song*, vpAllSongs, song )
|
|
|
|
|
{
|
|
|
|
|
// Ignore locked songs when choosing randomly
|
|
|
|
|
// TODO: Move Course initialization after UNLOCKMAN is created
|
|
|
|
|
if( UNLOCKMAN && UNLOCKMAN->SongIsLocked(*song) )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Ignore boring tutorial songs
|
|
|
|
|
if( (*song)->IsTutorial() )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Don't allow long songs
|
|
|
|
|
if( SONGMAN->GetNumStagesForSong(*song) > 1 )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
vpSongsOut.push_back( *song );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
2005-08-01 05:18:24 +00:00
|
|
|
if( m_bShuffle )
|
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-08-01 05:18:24 +00:00
|
|
|
const vector<CourseEntry> &entries = m_bShuffle? 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
|
|
|
|
2005-10-30 21:07:50 +00:00
|
|
|
vector<Song*> vpAllPossibleSongs;
|
2005-10-31 01:26:24 +00:00
|
|
|
bool bHaveAllSongs = false; // true if vpAllPossibleSongs has been generated
|
2005-10-30 21:07:50 +00:00
|
|
|
|
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
|
2005-10-30 21:07:50 +00:00
|
|
|
vector<Steps*> vpPossibleSteps;
|
|
|
|
|
|
2005-06-27 04:41:36 +00:00
|
|
|
if( e->pSong )
|
|
|
|
|
{
|
2005-10-30 21:07:50 +00:00
|
|
|
// Choose an exact song, if we have matching steps and all
|
|
|
|
|
e->pSong->GetSteps( vpPossibleSteps, st, e->baseDifficulty, e->iLowMeter, e->iHighMeter );
|
2005-10-31 00:27:48 +00:00
|
|
|
if( !vpPossibleSteps.empty() )
|
2005-10-30 21:07:50 +00:00
|
|
|
pResolvedSong = e->pSong;
|
|
|
|
|
else
|
|
|
|
|
continue;
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2005-10-31 01:26:24 +00:00
|
|
|
/* Generate vpAllPossibleSongs, if we havn't yet. */
|
|
|
|
|
if( !bHaveAllSongs )
|
|
|
|
|
{
|
|
|
|
|
GetSongsValidForRandom( vpAllPossibleSongs );
|
|
|
|
|
bHaveAllSongs = true;
|
|
|
|
|
}
|
|
|
|
|
|
2005-10-30 21:07:50 +00:00
|
|
|
// copy over any songs that match our group filter, if one is set, and match our
|
|
|
|
|
// steps filter.
|
2005-10-31 01:18:10 +00:00
|
|
|
vector<Song*> vpPossibleSongs;
|
2005-10-30 21:07:50 +00:00
|
|
|
FOREACH( Song*, vpAllPossibleSongs, song )
|
2005-06-27 04:41:36 +00:00
|
|
|
{
|
2005-10-30 21:07:50 +00:00
|
|
|
if( !e->sSongGroup.empty() && (*song)->m_sGroupName != e->sSongGroup )
|
2005-06-27 04:41:36 +00:00
|
|
|
continue;
|
2005-10-30 21:07:50 +00:00
|
|
|
|
|
|
|
|
vector<Steps*> vpMatchingSteps;
|
|
|
|
|
(*song)->GetSteps( vpMatchingSteps, st, e->baseDifficulty, e->iLowMeter, e->iHighMeter );
|
2006-03-27 21:53:33 +00:00
|
|
|
StepsUtil::RemoveLockedSteps( *song, vpMatchingSteps );
|
|
|
|
|
|
2005-10-30 21:07:50 +00:00
|
|
|
if( vpMatchingSteps.empty() )
|
2005-06-27 04:41:36 +00:00
|
|
|
continue;
|
2005-10-30 21:07:50 +00:00
|
|
|
|
|
|
|
|
vpPossibleSongs.push_back( *song );
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
|
|
|
|
|
2005-10-30 21:07:50 +00:00
|
|
|
// if there are no songs to choose from, abort now
|
|
|
|
|
if( vpPossibleSongs.empty() )
|
|
|
|
|
continue;
|
2005-06-27 04:41:36 +00:00
|
|
|
|
2005-10-31 00:54:52 +00:00
|
|
|
CourseSortSongs( e->songSort, vpPossibleSongs, rnd );
|
2006-02-21 01:36:49 +00:00
|
|
|
|
2006-02-21 02:29:22 +00:00
|
|
|
ASSERT( e->iChooseIndex >= 0 );
|
|
|
|
|
if( e->iChooseIndex < int(vpPossibleSongs.size()) )
|
2005-10-30 21:07:50 +00:00
|
|
|
pResolvedSong = vpPossibleSongs[e->iChooseIndex];
|
|
|
|
|
else
|
2005-06-27 04:41:36 +00:00
|
|
|
continue;
|
2006-02-21 01:36:49 +00:00
|
|
|
pResolvedSong->GetSteps( vpPossibleSteps, st, e->baseDifficulty, e->iLowMeter, e->iHighMeter );
|
2006-03-27 21:53:33 +00:00
|
|
|
StepsUtil::RemoveLockedSteps( pResolvedSong, vpPossibleSteps );
|
2005-06-27 04:41:36 +00:00
|
|
|
}
|
2003-02-24 00:09:34 +00:00
|
|
|
|
2005-10-30 21:07:50 +00:00
|
|
|
ASSERT( !vpPossibleSteps.empty() ); // if no steps are playable, this shouldn't be a possible song
|
2005-10-31 00:27:48 +00:00
|
|
|
random_shuffle( vpPossibleSteps.begin(), vpPossibleSteps.end(), rnd );
|
2005-10-30 21:07:50 +00:00
|
|
|
pResolvedSteps = vpPossibleSteps[0];
|
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;
|
2005-09-11 01:42:16 +00:00
|
|
|
te.bSecret = e->bSecret;
|
2005-06-27 04:41:36 +00:00
|
|
|
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
|
|
|
|
2005-08-01 05:18:24 +00:00
|
|
|
/* Hack: If any entry was non-FIXED, or m_bShuffle is set, then radar values
|
2004-08-05 20:03:03 +00:00
|
|
|
* 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
|
2005-08-01 05:18:24 +00:00
|
|
|
* otherwise unnecessary data (course entry types, m_bShuffle) to Trail, or
|
2004-08-05 20:03:03 +00:00
|
|
|
* storing a Course pointer in Trail (yuck)? */
|
2005-08-01 05:18:24 +00:00
|
|
|
if( !AllSongsAreFixed() || m_bShuffle )
|
2004-08-05 20:03:03 +00:00
|
|
|
{
|
|
|
|
|
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 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2005-09-09 02:22:18 +00:00
|
|
|
int Course::GetMeter( StepsType st, CourseDifficulty cd ) const
|
|
|
|
|
{
|
2006-02-14 11:16:39 +00:00
|
|
|
if( m_iCustomMeter[cd] != -1 )
|
2005-09-09 02:22:18 +00:00
|
|
|
return m_iCustomMeter[cd];
|
|
|
|
|
const Trail* pTrail = GetTrail( st );
|
|
|
|
|
if( pTrail != NULL )
|
|
|
|
|
return pTrail->GetMeter();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
{
|
2006-02-21 02:29:22 +00:00
|
|
|
if( !e->IsFixedSong() )
|
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 )
|
2005-08-01 05:18:24 +00:00
|
|
|
{
|
2004-07-24 06:40:51 +00:00
|
|
|
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 );
|
|
|
|
|
}
|
2005-08-01 05:18:24 +00:00
|
|
|
}
|
2004-07-24 06:40:51 +00:00
|
|
|
}
|
|
|
|
|
|
2006-02-21 01:54:50 +00:00
|
|
|
void Course::RegenerateNonFixedTrails() const
|
2004-07-24 06:40:51 +00:00
|
|
|
{
|
|
|
|
|
// 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.
|
2006-02-21 01:48:01 +00:00
|
|
|
if( !AllSongsAreFixed() )
|
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:
|
2006-02-21 01:48:01 +00:00
|
|
|
if( !AllSongsAreFixed() ) return SORT_LEVEL1_COLOR;
|
2005-05-19 20:34:35 +00:00
|
|
|
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:
|
2006-02-21 01:48:01 +00:00
|
|
|
if( !AllSongsAreFixed() ) return SORT_LEVEL1_COLOR;
|
2005-05-19 20:34:35 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2004-05-23 00:53:20 +00:00
|
|
|
bool Course::GetTotalSeconds( StepsType st, float& fSecondsOut ) const
|
2003-07-21 22:21:03 +00:00
|
|
|
{
|
2006-02-21 01:48:01 +00:00
|
|
|
if( !AllSongsAreFixed() )
|
2004-05-23 00:53:20 +00:00
|
|
|
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
|
|
|
|
|
{
|
2006-01-22 01:00:06 +00:00
|
|
|
vector<RString> rankingsongs;
|
2003-11-03 10:14:13 +00:00
|
|
|
|
|
|
|
|
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.
|
2006-02-21 01:48:01 +00:00
|
|
|
if( AllSongsAreFixed() )
|
2005-03-27 11:52:04 +00:00
|
|
|
{
|
|
|
|
|
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 ); }
|
|
|
|
|
|
2006-05-01 12:45:42 +00:00
|
|
|
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; }
|
2006-05-01 12:45:42 +00:00
|
|
|
static int HasMods( T* p, lua_State *L ) { lua_pushboolean(L, p->HasMods() ); return 1; }
|
|
|
|
|
static int GetCourseType( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCourseType() ); return 1; }
|
|
|
|
|
static int GetCourseEntry( T* p, lua_State *L ) { CourseEntry &ce = p->m_vEntries[IArg(1)]; ce.PushSelf(L); return 1; }
|
2005-10-23 07:52:02 +00:00
|
|
|
static int GetAllTrails( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
vector<Trail*> v;
|
|
|
|
|
p->GetAllTrails( v );
|
|
|
|
|
LuaHelpers::CreateTableFromArray<Trail*>( v, L );
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2006-05-01 12:45:42 +00:00
|
|
|
static int GetGroupName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sGroupName ); return 1; }
|
2005-02-21 17:20:11 +00:00
|
|
|
|
|
|
|
|
static void Register(lua_State *L)
|
|
|
|
|
{
|
2005-09-10 02:47:04 +00:00
|
|
|
ADD_METHOD( GetPlayMode );
|
|
|
|
|
ADD_METHOD( GetDisplayFullTitle );
|
|
|
|
|
ADD_METHOD( GetTranslitFullTitle );
|
|
|
|
|
ADD_METHOD( HasMods );
|
|
|
|
|
ADD_METHOD( GetCourseType );
|
|
|
|
|
ADD_METHOD( GetCourseEntry );
|
2005-10-23 07:52:02 +00:00
|
|
|
ADD_METHOD( GetAllTrails );
|
2006-05-01 12:45:42 +00:00
|
|
|
ADD_METHOD( GetGroupName );
|
2005-08-01 05:18:24 +00:00
|
|
|
|
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.
|
|
|
|
|
*/
|