Files
itgmania212121/stepmania/src/Course.cpp
T

976 lines
27 KiB
C++
Raw Normal View History

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"
2006-08-19 23:35:31 +00:00
#include "SongCacheIndex.h"
2002-07-23 01:41:40 +00:00
#include "GameManager.h"
#include "GameState.h"
2002-07-27 19:29:51 +00:00
#include "RageLog.h"
2003-08-03 00:13:55 +00:00
#include "Steps.h"
#include "ThemeManager.h"
#include "ProfileManager.h"
#include "Foreach.h"
#include "UnlockManager.h"
2005-07-31 05:41:32 +00:00
#include "CourseLoaderCRS.h"
2006-09-29 09:54:50 +00:00
#include "LuaManager.h"
2006-01-07 04:11:29 +00:00
#include "LocalizedString.h"
#include "Preference.h"
2006-08-19 23:35:31 +00:00
#include <limits.h>
static Preference<int> MAX_SONGS_IN_EDIT_COURSE( "MaxSongsInEditCourse", -1 );
2005-06-26 21:31:07 +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 );
2006-09-26 07:13:54 +00:00
LuaXType( CourseType );
2006-09-26 04:08:12 +00:00
LuaFunction( CourseTypeToLocalizedString, CourseTypeToLocalizedString( Enum::Check<CourseType>( L, 1 ) ) );
2005-08-01 05:18:24 +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;
#define SORT_PREFERRED_COLOR THEME->GetMetricC("Course","SortPreferredColor")
#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")
2004-10-07 22:20:52 +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" );
2006-06-13 03:51:18 +00:00
if( !songCriteria.m_sGroupName.empty() )
vsEntryDescription.push_back( songCriteria.m_sGroupName );
2006-07-11 06:51:46 +00:00
if( songCriteria.m_bUseSongGenreAllowedList )
vsEntryDescription.push_back( join(",",songCriteria.m_vsSongGenreAllowedList) );
2006-10-07 04:39:48 +00:00
if( stepsCriteria.m_difficulty != Difficulty_Invalid && stepsCriteria.m_difficulty != DIFFICULTY_MEDIUM )
2006-06-13 03:51:18 +00:00
vsEntryDescription.push_back( DifficultyToLocalizedString(stepsCriteria.m_difficulty) );
if( stepsCriteria.m_iLowMeter != -1 )
vsEntryDescription.push_back( ssprintf("Low meter: %d", stepsCriteria.m_iLowMeter) );
if( stepsCriteria.m_iHighMeter != -1 )
vsEntryDescription.push_back( ssprintf("High meter: %d", stepsCriteria.m_iHighMeter) );
2005-07-30 04:50:17 +00:00
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:
static int GetSong( T* p, lua_State *L )
{
if( p->pSong )
p->pSong->PushSelf(L);
else
lua_pushnil(L);
return 1;
}
2006-09-27 19:53:05 +00:00
LunaCourseEntry()
2005-08-03 03:12:09 +00:00
{
2006-09-27 19:53:05 +00:00
ADD_METHOD( GetSong );
2005-08-03 03:12:09 +00:00
}
};
LUA_REGISTER_CLASS( CourseEntry )
// lua end
2005-07-30 04:50:17 +00:00
Course::Course()
{
Init();
}
2002-07-02 17:34:20 +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 )
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-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;
}
}
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;
case COURSE_TYPE_NONSTOP: return PLAY_MODE_NONSTOP;
2006-09-26 20:32:41 +00:00
default: ASSERT(0); return PlayMode_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 );
}
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;
m_bSortByMeter = false;
2005-06-28 08:11:30 +00:00
m_vEntries.clear();
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 = "";
2006-10-07 03:32:16 +00:00
m_LoadedFromProfile = ProfileSlot_Invalid;
m_iTrailCacheSeed = 0;
2004-01-26 20:55:35 +00:00
}
2002-07-23 01:41:40 +00:00
bool Course::IsPlayableIn( StepsType st ) const
{
return GetTrail( st ) != NULL;
}
2003-08-04 21:34:45 +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
* results. Returned pointers remain valid for the lifetime of the Course. If the
* course difficulty doesn't exist, NULL is returned. */
Trail* Course::GetTrail( StepsType st, CourseDifficulty cd ) const
2002-07-23 01:41:40 +00:00
{
2006-10-07 10:49:53 +00:00
ASSERT( cd != CourseDifficulty_Invalid );
2004-06-03 08:22:02 +00:00
//
// Check to see if the Trail cache is out of date
//
if( m_iTrailCacheSeed != GAMESTATE->m_iStageSeed )
{
RegenerateNonFixedTrails();
m_iTrailCacheSeed = GAMESTATE->m_iStageSeed;
}
//
// Look in the Trail cache
//
{
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;
}
}
2005-08-01 05:18:24 +00:00
return GetTrailForceRegenCache( st, cd );
}
Trail* Course::GetTrailForceRegenCache( StepsType st, CourseDifficulty cd ) const
{
//
// Construct a new Trail, add it to the cache, then return it.
//
CacheData &cache = m_TrailCache[ CacheEntry(st, cd) ];
Trail &trail = cache.trail;
2004-06-02 07:15:19 +00:00
trail = Trail();
if( !GetTrailSorted( st, cd, trail ) || trail.m_vEntries.empty() )
{
/* This course difficulty doesn't exist. */
cache.null = true;
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);
}
}
cache.null = false;
return &cache.trail;
2004-06-03 08:22:02 +00:00
}
bool Course::GetTrailSorted( StepsType st, CourseDifficulty cd, Trail &trail ) const
2004-06-03 08:22:02 +00:00
{
if( !GetTrailUnsorted( st, cd, trail ) )
return false;
if( this->m_bSortByMeter )
{
/* Sort according to DIFFICULTY_MEDIUM, since the order of songs
* must not change across difficulties. */
Trail SortTrail;
2006-10-07 10:49:53 +00:00
if( cd == COURSE_DIFFICULTY_REGULAR )
SortTrail = trail;
else
{
2006-10-07 10:49:53 +00:00
bool bOK = GetTrailUnsorted( st, COURSE_DIFFICULTY_REGULAR, SortTrail );
2006-10-07 10:49:53 +00:00
/* If we have any other difficulty, we must have COURSE_DIFFICULTY_REGULAR. */
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())) );
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;
}
return true;
}
// TODO: Move Course initialization after PROFILEMAN is created
2006-07-03 00:05:13 +00:00
static void CourseSortSongs( SongSort sort, vector<Song*> &vpPossibleSongs, RandomGen &rnd )
{
switch( sort )
{
DEFAULT_FAIL(sort);
case SongSort_Randomize:
2006-07-03 00:05:13 +00:00
random_shuffle( vpPossibleSongs.begin(), vpPossibleSongs.end(), rnd );
break;
case SongSort_MostPlays:
2006-07-03 00:05:13 +00:00
if( PROFILEMAN )
SongUtil::SortSongPointerArrayByNumPlays( vpPossibleSongs, PROFILEMAN->GetMachineProfile(), true ); // descending
break;
case SongSort_FewestPlays:
2006-07-03 00:05:13 +00:00
if( PROFILEMAN )
SongUtil::SortSongPointerArrayByNumPlays( vpPossibleSongs, PROFILEMAN->GetMachineProfile(), false ); // ascending
break;
case SongSort_TopGrades:
2006-07-03 00:05:13 +00:00
SongUtil::SortSongPointerArrayByGrades( vpPossibleSongs, true ); // descending
break;
case SongSort_LowestGrades:
2006-07-03 00:05:13 +00:00
SongUtil::SortSongPointerArrayByGrades( vpPossibleSongs, false ); // ascending
break;
}
}
2006-07-30 00:09:16 +00:00
namespace
{
struct SongIsEqual
{
const Song *m_pSong;
SongIsEqual( const Song *pSong ) : m_pSong(pSong) { }
bool operator()( const SongAndSteps &sas ) const { return sas.pSong == m_pSong; }
};
}
bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const
{
2004-06-03 08:22:02 +00:00
trail.Init();
switch( cd )
{
2006-10-07 10:49:53 +00:00
case COURSE_DIFFICULTY_BEGINNER:
case COURSE_DIFFICULTY_CHALLENGE:
return false;
}
2006-07-30 11:36:18 +00:00
//
// Construct a new Trail, add it to the cache, then return it.
//
/* Different seed for each course, but the same for the whole round: */
RandomGen rnd( GAMESTATE->m_iStageSeed + GetHashForString(m_sMainTitle) );
2003-12-21 02:54:23 +00:00
vector<CourseEntry> tmp_entries;
2005-08-01 05:18:24 +00:00
if( m_bShuffle )
{
/* 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 );
}
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;
trail.m_StepsType = st;
2004-05-23 09:17:10 +00:00
trail.m_CourseDifficulty = cd;
2003-07-21 21:54:07 +00:00
/* Set to true if CourseDifficulty is able to change something. */
2006-10-07 10:49:53 +00:00
bool bCourseDifficultyIsSignificant = (cd == COURSE_DIFFICULTY_REGULAR);
vector<Song*> vpAllPossibleSongs;
vector<SongAndSteps> vSongAndSteps;
// Resolve each entry to a Song and Steps.
FOREACH_CONST( CourseEntry, entries, e )
{
SongAndSteps resolved; // fill this in
SongCriteria soc = e->songCriteria;
if( e->pSong )
{
soc.m_bUseSongAllowedList = true;
soc.m_vpSongAllowedList.push_back( e->pSong );
}
soc.m_Tutorial = SongCriteria::Tutorial_No;
soc.m_Locked = SongCriteria::Locked_Unlocked;
if( !soc.m_bUseSongAllowedList )
soc.m_iStagesForSong = 1;
StepsCriteria stc = e->stepsCriteria;
stc.m_st = st;
stc.m_Locked = StepsCriteria::Locked_Unlocked;
const bool bSameSongCriteria = e != entries.begin() && (e-1)->songCriteria == soc;
const bool bSameStepsCriteria = e != entries.begin() && (e-1)->stepsCriteria == stc;
if( e->pSong )
{
StepsUtil::GetAllMatching( e->pSong, stc, vSongAndSteps );
}
else if( vSongAndSteps.empty() || !(bSameSongCriteria && bSameStepsCriteria) )
{
vSongAndSteps.clear();
StepsUtil::GetAllMatching( soc, stc, vSongAndSteps );
}
// It looks bad to have the same song 2x in a row in a randomly generated course.
// Don't allow the same song to be played 2x in a row, unless there's only
// one song in vpPossibleSongs.
if( trail.m_vEntries.size() > 0 && vSongAndSteps.size() > 1 )
{
2006-07-30 00:09:16 +00:00
const TrailEntry &teLast = trail.m_vEntries.back();
2006-07-30 00:09:16 +00:00
RemoveIf( vSongAndSteps, SongIsEqual(teLast.pSong) );
}
// if there are no songs to choose from, abort this CourseEntry
if( vSongAndSteps.empty() )
continue;
2006-07-03 00:05:13 +00:00
vector<Song*> vpSongs;
typedef vector<Steps*> StepsVector;
map<Song*,StepsVector> mapSongToSteps;
FOREACH_CONST( SongAndSteps, vSongAndSteps, sas )
{
StepsVector &v = mapSongToSteps[sas->pSong];
v.push_back( sas->pSteps );
if( v.size() == 1 )
vpSongs.push_back( sas->pSong );
2006-07-03 00:05:13 +00:00
}
CourseSortSongs( e->songSort, vpSongs, rnd );
ASSERT( e->iChooseIndex >= 0 );
if( e->iChooseIndex < int(vSongAndSteps.size()) )
2006-07-03 00:05:13 +00:00
{
resolved.pSong = vpSongs[e->iChooseIndex];
const vector<Steps*> &vpSongs = mapSongToSteps[resolved.pSong];
resolved.pSteps = vpSongs[ RandomInt(vpSongs.size()) ];
2006-07-03 00:05:13 +00:00
}
else
2006-07-03 00:05:13 +00:00
{
continue;
2006-07-03 00:05:13 +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 = resolved.pSteps->GetDifficulty();
2006-06-13 03:51:18 +00:00
int iLowMeter = e->stepsCriteria.m_iLowMeter;
int iHighMeter = e->stepsCriteria.m_iHighMeter;
2006-10-07 10:49:53 +00:00
if( cd != COURSE_DIFFICULTY_REGULAR && !e->bNoDifficult )
{
2006-10-07 10:49:53 +00:00
Difficulty new_dc = (Difficulty)(dc + cd - COURSE_DIFFICULTY_REGULAR);
new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(DIFFICULTY_EDIT-1) );
bool bChangedDifficulty = false;
if( new_dc != dc )
{
Steps* pNewSteps = SongUtil::GetStepsByDifficulty( resolved.pSong, st, new_dc );
if( pNewSteps )
{
dc = new_dc;
resolved.pSteps = pNewSteps;
bChangedDifficulty = true;
bCourseDifficultyIsSignificant = true;
}
}
/* 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. */
2006-10-07 04:39:48 +00:00
if( e->stepsCriteria.m_difficulty == Difficulty_Invalid && bChangedDifficulty )
{
/* Minimum and maximum to add to make the meter range contain the actual
* meter: */
int iMinDist = resolved.pSteps->GetMeter() - iHighMeter;
int iMaxDist = resolved.pSteps->GetMeter() - iLowMeter;
/* Clamp the possible adjustments to try to avoid going under 1 or over
* MAX_BOTTOM_RANGE. */
iMinDist = min( max( iMinDist, -iLowMeter+1 ), iMaxDist );
iMaxDist = max( min( iMaxDist, MAX_BOTTOM_RANGE-iHighMeter ), iMinDist );
int iAdd;
if( iMaxDist == iMinDist )
iAdd = iMaxDist;
else
iAdd = rnd(iMaxDist-iMinDist) + iMinDist;
iLowMeter += iAdd;
iHighMeter += iAdd;
}
}
TrailEntry te;
te.pSong = resolved.pSong;
te.pSteps = resolved.pSteps;
te.Modifiers = e->sModifiers;
te.Attacks = e->attacks;
te.bSecret = e->bSecret;
te.iLowMeter = iLowMeter;
te.iHighMeter = iHighMeter;
2006-10-07 04:39:48 +00:00
/* If we chose based on meter (not difficulty), then store Difficulty_Invalid, so
* other classes can tell that we used meter. */
2006-10-07 04:39:48 +00:00
if( e->stepsCriteria.m_difficulty == Difficulty_Invalid )
{
2006-10-07 04:39:48 +00:00
te.dc = Difficulty_Invalid;
}
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;
}
trail.m_vEntries.push_back( te );
if( IsAnEdit() && MAX_SONGS_IN_EDIT_COURSE > 0 &&
int(trail.m_vEntries.size()) >= MAX_SONGS_IN_EDIT_COURSE )
{
break;
}
}
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
* 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
* storing a Course pointer in Trail (yuck)? */
2005-08-01 05:18:24 +00:00
if( !AllSongsAreFixed() || m_bShuffle )
{
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];
/* If the course difficulty never actually changed anything, then this difficulty
2006-10-07 10:49:53 +00:00
* is equivalent to COURSE_DIFFICULTY_REGULAR; it doesn't exist. */
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
{
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 );
}
}
int Course::GetMeter( StepsType st, CourseDifficulty cd ) const
{
2006-02-14 11:16:39 +00:00
if( m_iCustomMeter[cd] != -1 )
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
{
if( !e->sModifiers.empty() || !e->attacks.empty() )
2004-03-13 23:11:57 +00:00
return true;
}
return false;
}
bool Course::AllSongsAreFixed() const
{
2005-06-28 08:11:30 +00:00
FOREACH_CONST( CourseEntry, m_vEntries, e )
{
if( !e->IsFixedSong() )
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 )
{
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 ) )
m_TrailCache.erase( it );
}
2005-08-01 05:18:24 +00:00
}
2004-07-24 06:40:51 +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.
if( !AllSongsAreFixed() )
m_TrailCache.clear();
2002-07-27 19:29:51 +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.
int iMeter = 5;
2005-05-19 20:34:35 +00:00
switch( PREFSMAN->m_CourseSortOrder )
2003-07-27 14:54:56 +00:00
{
2006-10-07 07:43:18 +00:00
case COURSE_SORT_PREFERRED:
return SORT_PREFERRED_COLOR; //This will also be used for autogen'd courses in some cases.
2006-10-07 07:43:18 +00:00
case COURSE_SORT_SONGS:
if( m_vEntries.size() >= 7 ) return SORT_LEVEL2_COLOR;
else if( m_vEntries.size() >= 4 ) return SORT_LEVEL4_COLOR;
else return SORT_LEVEL5_COLOR;
2003-07-27 14:54:56 +00:00
2006-10-07 07:43:18 +00:00
case COURSE_SORT_METER:
if( !AllSongsAreFixed() ) 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;
else return SORT_LEVEL5_COLOR;
2003-07-27 14:54:56 +00:00
2006-10-07 07:43:18 +00:00
case COURSE_SORT_METER_SUM:
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;
else return SORT_LEVEL5_COLOR;
2003-07-27 14:54:56 +00:00
2006-10-07 07:43:18 +00:00
case COURSE_SORT_RANK:
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;
else return SORT_LEVEL4_COLOR;
2003-07-27 19:14:05 +00:00
default:
FAIL_M( ssprintf("Invalid course sort %d.", int(PREFSMAN->m_CourseSortOrder)) );
2003-07-27 19:14:05 +00:00
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
}
bool Course::GetTotalSeconds( StepsType st, float& fSecondsOut ) const
{
if( !AllSongsAreFixed() )
return false;
2006-10-07 10:49:53 +00:00
Trail* pTrail = GetTrail( st, COURSE_DIFFICULTY_REGULAR );
fSecondsOut = pTrail->GetLengthSeconds();
return true;
2002-12-17 05:23:45 +00:00
}
bool Course::CourseHasBestOrWorst() const
{
2005-06-28 08:11:30 +00:00
FOREACH_CONST( CourseEntry, m_vEntries, e )
{
2005-06-28 08:11:30 +00:00
if( e->iChooseIndex == SongSort_MostPlays && e->iChooseIndex != -1 )
return true;
2005-06-28 08:11:30 +00:00
if( e->iChooseIndex == SongSort_FewestPlays && e->iChooseIndex != -1 )
return true;
}
return false;
}
bool Course::HasBanner() const
{
return m_sBannerPath != "" && IsAFile(m_sBannerPath);
}
void Course::UpdateCourseStats( StepsType st )
{
m_SortOrder_TotalDifficulty = 0;
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;
if ( m_SortOrder_Ranking == 2 )
m_SortOrder_Ranking = 3;
m_SortOrder_TotalDifficulty = INT_MAX;
2003-08-04 20:45:34 +00:00
return;
}
2006-10-07 10:49:53 +00:00
const Trail* pTrail = GetTrail( st, COURSE_DIFFICULTY_REGULAR );
2004-06-20 16:54:51 +00:00
m_SortOrder_TotalDifficulty += pTrail != NULL? pTrail->GetTotalMeter():0;
// 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(),
m_SortOrder_TotalDifficulty );
2003-10-25 22:55:11 +00:00
}
bool Course::IsRanking() const
{
2006-01-22 01:00:06 +00:00
vector<RString> 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
}
const CourseEntry *Course::FindFixedSong( const Song *pSong ) const
{
2005-06-28 08:11:30 +00:00
FOREACH_CONST( CourseEntry, m_vEntries, e )
{
const CourseEntry &entry = *e;
if( entry.pSong == pSong )
return &entry;
}
return NULL;
}
2004-08-30 22:15:57 +00:00
void Course::GetAllCachedTrails( vector<Trail *> &out )
{
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;
}
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( AllSongsAreFixed() )
{
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
2006-05-01 21:49:59 +00:00
bool Course::Matches( RString sGroup, RString sCourse ) const
{
if( sGroup.size() && sGroup.CompareNoCase(this->m_sGroupName) != 0)
return false;
RString sFile = m_sPath;
2006-05-04 07:09:08 +00:00
if( !sFile.empty() )
{
sFile.Replace("\\","/");
vector<RString> bits;
split( sFile, "/", bits );
const RString &sLastBit = bits[bits.size()-1];
if( sCourse.EqualsNoCase(sLastBit) )
return true;
}
2006-05-01 21:49:59 +00:00
if( sCourse.EqualsNoCase(this->GetTranslitFullTitle()) )
return true;
return false;
}
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:
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; }
2006-08-16 22:54:28 +00:00
static int IsAutogen( T* p, lua_State *L ) { lua_pushboolean(L, p->m_bIsAutogen ); return 1; }
2006-08-19 05:04:17 +00:00
static int GetEstimatedNumStages( T* p, lua_State *L ) { lua_pushnumber(L, p->GetEstimatedNumStages() ); return 1; }
2006-09-01 03:05:47 +00:00
static int GetTotalSeconds( T* p, lua_State *L )
{
2006-09-27 06:09:52 +00:00
StepsType st = Enum::Check<StepsType>(L, 1);
2006-09-07 22:11:54 +00:00
float fTotalSeconds;
if( !p->GetTotalSeconds(st, fTotalSeconds) )
lua_pushnil( L );
else
lua_pushnumber( L, fTotalSeconds );
2006-09-01 03:05:47 +00:00
return 1;
}
2005-02-21 17:20:11 +00:00
2006-09-27 19:53:05 +00:00
LunaCourse()
2005-02-21 17:20:11 +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 );
2006-08-16 22:54:28 +00:00
ADD_METHOD( IsAutogen );
2006-08-19 05:04:17 +00:00
ADD_METHOD( GetEstimatedNumStages );
2006-09-01 03:05:47 +00:00
ADD_METHOD( GetTotalSeconds );
2005-02-21 17:20:11 +00:00
}
};
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.
*/