Ejemplo n.º 1
0
bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const
{
	trail.Init();

	// XXX: Why are beginner and challenge excluded here? -Wolfman2000
	// No idea, probably an obsolete design decision from ITG, removing
	// exclusion here, but there's some other area that prevents it too. -Kyz
	/*
	switch( cd )
	{
		case Difficulty_Beginner:
			return false;
		case Difficulty_Challenge:
			return false;
		default: break;
	}
	*/

	// 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) );

	vector<CourseEntry> tmp_entries;
	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. */
		tmp_entries = m_vEntries;
		random_shuffle( tmp_entries.begin(), tmp_entries.end(), rnd );
	}

	const vector<CourseEntry> &entries = m_bShuffle ? tmp_entries:m_vEntries;

	// This can take some time, so don't fill it out unless we need it.
	vector<Song*> vSongsByMostPlayed;
	vector<Song*> AllSongsShuffled;

	trail.m_StepsType = st;
	trail.m_CourseType = GetCourseType();
	trail.m_CourseDifficulty = cd;
	trail.m_vEntries.reserve(entries.size());

	// Set to true if CourseDifficulty is able to change something.
	bool bCourseDifficultyIsSignificant = (cd == Difficulty_Medium);

	

	// Resolve each entry to a Song and Steps.
	if( trail.m_CourseType == COURSE_TYPE_ENDLESS )
	{
		GetTrailUnsortedEndless(entries, trail, st, cd, rnd, bCourseDifficultyIsSignificant);
	}
	else
	{
		vector<SongAndSteps> vSongAndSteps;
		for (auto e = entries.begin(); e != entries.end(); ++e)
		{
			SongAndSteps resolved;	// fill this in
			SongCriteria soc = e->songCriteria;

			Song *pSong = e->songID.ToSong();
			if( pSong )
			{
				soc.m_bUseSongAllowedList = true;
				soc.m_vpSongAllowedList.push_back( pSong );
			}
			soc.m_Tutorial = SongCriteria::Tutorial_No;
			soc.m_Locked = SongCriteria::Locked_Unlocked;
			if( !soc.m_bUseSongAllowedList )
				soc.m_iMaxStagesForSong = 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( pSong )
			{
				StepsUtil::GetAllMatching( 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 )
			{
				const TrailEntry &teLast = trail.m_vEntries.back();
				RemoveIf( vSongAndSteps, SongIsEqual( teLast.pSong ) );
			}

			// if there are no songs to choose from, abort this CourseEntry
			if( vSongAndSteps.empty() )
				continue;

			vector<Song*> vpSongs;
			typedef vector<Steps*> StepsVector;
			std::map<Song*, StepsVector> mapSongToSteps;
			for (auto &sas: vSongAndSteps)
			{
				StepsVector &v = mapSongToSteps[ sas.pSong ];

				v.push_back( sas.pSteps );
				if( v.size() == 1 )
					vpSongs.push_back( sas.pSong );
			}

			CourseSortSongs( e->songSort, vpSongs, rnd );

			ASSERT( e->iChooseIndex >= 0 );
			if( e->iChooseIndex < int( vSongAndSteps.size() ) )
			{
				resolved.pSong = vpSongs[ e->iChooseIndex ];
				const vector<Steps*> &mappedSongs = mapSongToSteps[ resolved.pSong ];
				resolved.pSteps = mappedSongs[ RandomInt( mappedSongs.size() ) ];
			}
			else
			{
				continue;
			}

			/* 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();
			int iLowMeter = e->stepsCriteria.m_iLowMeter;
			int iHighMeter = e->stepsCriteria.m_iHighMeter;
			if( cd != Difficulty_Medium  &&  !e->bNoDifficult )
			{
				Difficulty new_dc = ( Difficulty )( dc + cd - Difficulty_Medium );
				new_dc = Rage::clamp( new_dc, ( Difficulty )0, ( Difficulty )( Difficulty_Edit - 1 ) );
				/*
				// re-edit this code to work using the metric.
				Difficulty new_dc;
				if( INCLUDE_BEGINNER_STEPS )
				{
				// don't factor in the course difficulty if we're including
				// beginner steps -aj
				new_dc = Rage::clamp( dc, Difficulty_Beginner, (Difficulty)(Difficulty_Edit-1) );
				}
				else
				{
				new_dc = (Difficulty)(dc + cd - Difficulty_Medium);
				new_dc = Rage::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. */
				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 = std::min( std::max( iMinDist, -iLowMeter + 1 ), iMaxDist );
					iMaxDist = std::max( std::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;

			/* If we chose based on meter (not difficulty), then store Difficulty_Invalid, so
			* other classes can tell that we used meter. */
			if( e->stepsCriteria.m_difficulty == Difficulty_Invalid )
			{
				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 );

			// LOG->Trace( "Chose: %s, %d", te.pSong->GetSongDir().c_str(), te.pSteps->GetMeter() );

			if( IsAnEdit() && MAX_SONGS_IN_EDIT_COURSE > 0 &&
				int( trail.m_vEntries.size() ) >= MAX_SONGS_IN_EDIT_COURSE )
			{
				break;
			}
		}
	}

	/* 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
	 * otherwise unnecessary data (course entry types, m_bShuffle) to Trail, or
	 * storing a Course pointer in Trail (yuck)? */
	if( !AllSongsAreFixed() || m_bShuffle )
	{
		trail.m_bRadarValuesCached = true;
		trail.m_CachedRadarValues = RadarValues();
	}

	/* If we have a manually-entered meter for this difficulty, use it. */
	if( m_iCustomMeter[cd] != -1 )
		trail.m_iSpecifiedMeter = m_iCustomMeter[cd];

	/* If the course difficulty never actually changed anything, then this difficulty
	 * is equivalent to Difficulty_Medium; it doesn't exist. */
	return bCourseDifficultyIsSignificant && trail.m_vEntries.size() > 0;
}
Ejemplo n.º 2
0
bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const
{
	trail.Init();

	// XXX: Why are beginner and challenge excluded here? -Wolfman2000
	switch( cd )
	{
		case Difficulty_Beginner:
			return false;
		case Difficulty_Challenge:
			return false;
		default: break;
	}

	// 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) );

	vector<CourseEntry> tmp_entries;
	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. */
		tmp_entries = m_vEntries;
		random_shuffle( tmp_entries.begin(), tmp_entries.end(), rnd );
	}

	const vector<CourseEntry> &entries = m_bShuffle ? tmp_entries:m_vEntries;

	// This can take some time, so don't fill it out unless we need it.
	vector<Song*> vSongsByMostPlayed;
	vector<Song*> AllSongsShuffled;

	trail.m_StepsType = st;
	trail.m_CourseType = GetCourseType();
	trail.m_CourseDifficulty = cd;

	// Set to true if CourseDifficulty is able to change something.
	bool bCourseDifficultyIsSignificant = (cd == Difficulty_Medium);

	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;

		Song *pSong = e->songID.ToSong();
		if( pSong )
		{
			soc.m_bUseSongAllowedList = true;
			soc.m_vpSongAllowedList.push_back( pSong );
		}
		soc.m_Tutorial = SongCriteria::Tutorial_No;
		soc.m_Locked = SongCriteria::Locked_Unlocked;
		if( !soc.m_bUseSongAllowedList )
			soc.m_iMaxStagesForSong = 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( pSong )
		{
			StepsUtil::GetAllMatching( 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 )
		{
			const TrailEntry &teLast = trail.m_vEntries.back();
			RemoveIf( vSongAndSteps, SongIsEqual(teLast.pSong) );
		}

		// if there are no songs to choose from, abort this CourseEntry
		if( vSongAndSteps.empty() )
			continue;

		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 );
		}