Files
itgmania212121/stepmania/src/ScreenSelectMusic.cpp
T
Glenn Maynard 546e048fa2 Support background loading. The principle is simple: when the song
changes, request all of its assets that we need be loaded.  Once each
piece finishes, load it from cache.  We still take a skip to decode graphics
(banners in particular), but not from disk seeks, which is an issue on hard
drives and fatal on CDs.

This also handles fading banners much more precisely: we fade to the
high res banner once the music wheel has settled.  Before, it was approximate;
we'd often load the banner before we settled.  It still skips, but it's much less
prominent to skip when the music wheel is completely settled, than to skip
just before it settles.

Some more work is necessary to resolve lock contention; currently, you'll
still see frequent skips off a CD (but not constant as before).  I'm working
on that, but I'm not sure if I want to put that in before the release, since
it's caused some crashes that I havn't tracked down.

The actual background work is disabled, and may not be enabled for the
release.  I'm committing this to get the basic API work ironed out.  It still
gives perceptual skip improvements even without the background thread.
2004-09-08 02:53:54 +00:00

1774 lines
49 KiB
C++

#include "global.h"
#include "ScreenSelectMusic.h"
#include "ScreenManager.h"
#include "PrefsManager.h"
#include "SongManager.h"
#include "GameManager.h"
#include "GameSoundManager.h"
#include "GameConstantsAndTypes.h"
#include "PrefsManager.h"
#include "RageLog.h"
#include "InputMapper.h"
#include "GameState.h"
#include "CodeDetector.h"
#include "ThemeManager.h"
#include "Steps.h"
#include "ActorUtil.h"
#include "RageDisplay.h"
#include "RageTextureManager.h"
#include "Course.h"
#include "ProfileManager.h"
#include "MenuTimer.h"
#include "LightsManager.h"
#include "StageStats.h"
#include "StepsUtil.h"
#include "Foreach.h"
const int NUM_SCORE_DIGITS = 9;
#define FOV THEME->GetMetricF(m_sName,"FOV")
#define FOV_CENTER_X THEME->GetMetricF(m_sName,"FOVCenterX")
#define FOV_CENTER_Y THEME->GetMetricF(m_sName,"FOVCenterY")
#define BANNER_WIDTH THEME->GetMetricF(m_sName,"BannerWidth")
#define BANNER_HEIGHT THEME->GetMetricF(m_sName,"BannerHeight")
#define SONG_OPTIONS_EXTRA_COMMAND THEME->GetMetric (m_sName,"SongOptionsExtraCommand")
#define SAMPLE_MUSIC_DELAY THEME->GetMetricF(m_sName,"SampleMusicDelay")
#define SHOW_RADAR THEME->GetMetricB(m_sName,"ShowRadar")
#define SHOW_GRAPH THEME->GetMetricB(m_sName,"ShowGraph")
#define SHOW_PANES THEME->GetMetricB(m_sName,"ShowPanes")
#define SHOW_DIFFICULTY_LIST THEME->GetMetricB(m_sName,"ShowDifficultyList")
#define PREV_SCREEN THEME->GetMetric (m_sName,"PrevScreen")
#define NEXT_SCREEN THEME->GetMetric (m_sName,"NextScreen")
#define NEXT_OPTIONS_SCREEN THEME->GetMetric (m_sName,"NextOptionsScreen")
#define SCORE_SORT_CHANGE_COMMAND(i) THEME->GetMetric (m_sName,ssprintf("ScoreP%iSortChangeCommand", i+1))
#define SCORE_FRAME_SORT_CHANGE_COMMAND(i) THEME->GetMetric (m_sName,ssprintf("ScoreFrameP%iSortChangeCommand", i+1))
#define DO_ROULETTE_ON_MENU_TIMER THEME->GetMetricB(m_sName,"DoRouletteOnMenuTimer")
#define ALIGN_MUSIC_BEATS THEME->GetMetricB(m_sName,"AlignMusicBeat")
#define CODES THEME->GetMetric (m_sName,"Codes")
static const ScreenMessage SM_AllowOptionsMenuRepeat = ScreenMessage(SM_User+1);
CString g_sFallbackCDTitlePath;
static CString g_sCDTitlePath;
static bool g_bWantFallbackCdTitle;
static bool g_bCDTitleWaiting = false;
static CString g_sBannerPath;
static bool g_bBannerWaiting = false;
static bool g_bSampleMusicWaiting = false;
static RageTimer g_StartedLoadingAt(RageZeroTimer);
ScreenSelectMusic::ScreenSelectMusic( CString sClassName ) : ScreenWithMenuElements( sClassName )
{
LOG->Trace( "ScreenSelectMusic::ScreenSelectMusic()" );
LIGHTSMAN->SetLightsMode( LIGHTSMODE_MENU );
m_DisplayMode = GAMESTATE->IsCourseMode() ? DISPLAY_COURSES : DISPLAY_SONGS;
/* Finish any previous stage. It's OK to call this when we havn't played a stage yet.
* Do this before anything that might look at GAMESTATE->m_iCurrentStageIndex. */
GAMESTATE->FinishStage();
/* Cache: */
g_sFallbackCDTitlePath = THEME->GetPathG(m_sName,"fallback cdtitle");
if( GAMESTATE->m_pCurStyle == NULL )
RageException::Throw( "The Style has not been set. A theme must set the Style before loading ScreenSelectMusic." );
if( GAMESTATE->m_PlayMode == PLAY_MODE_INVALID )
RageException::Throw( "The PlayMode has not been set. A theme must set the PlayMode before loading ScreenSelectMusic." );
m_MusicWheel.Load();
// pop'n music has this under the songwheel...
FOREACH_PlayerNumber( p )
{
m_sprCharacterIcon[p].SetName( ssprintf("CharacterIconP%d",p+1) );
Character* pChar = GAMESTATE->m_pCurCharacters[p];
CString sPath = pChar->GetSongSelectIconPath();
if( sPath.empty() )
continue;
m_sprCharacterIcon[p].Load( sPath );
SET_XY( m_sprCharacterIcon[p] );
this->AddChild( &m_sprCharacterIcon[p] );
}
m_MusicWheelUnder.Load( THEME->GetPathG(m_sName,"wheel under") );
m_MusicWheelUnder->SetName( "WheelUnder" );
SET_XY( m_MusicWheelUnder );
this->AddChild( m_MusicWheelUnder );
m_MusicWheel.SetName( "MusicWheel", "Wheel" );
SET_XY( m_MusicWheel );
this->AddChild( &m_MusicWheel );
m_sprBannerMask.SetName( "Banner" ); // use the same metrics and animation as Banner
m_sprBannerMask.Load( THEME->GetPathG(m_sName,"banner mask") );
m_sprBannerMask.SetBlendMode( BLEND_NO_EFFECT ); // don't draw to color buffer
m_sprBannerMask.SetZWrite( true ); // do draw to the zbuffer
SET_XY( m_sprBannerMask );
this->AddChild( &m_sprBannerMask );
m_sprExplanation.Load( THEME->GetPathG(m_sName,"explanation") );
m_sprExplanation->SetName( "Explanation" );
SET_XY( m_sprExplanation );
this->AddChild( m_sprExplanation );
// this is loaded SetSong and TweenToSong
m_Banner.SetName( "Banner" );
m_Banner.SetZTestMode( ZTEST_WRITE_ON_PASS ); // do have to pass the z test
m_Banner.ScaleToClipped( BANNER_WIDTH, BANNER_HEIGHT );
SET_XY( m_Banner );
this->AddChild( &m_Banner );
m_sprBannerFrame.Load( THEME->GetPathG(m_sName,"banner frame") );
m_sprBannerFrame->SetName( "BannerFrame" );
SET_XY( m_sprBannerFrame );
this->AddChild( m_sprBannerFrame );
m_BPMDisplay.SetName( "BPMDisplay" );
m_BPMDisplay.Load();
SET_XY( m_BPMDisplay );
this->AddChild( &m_BPMDisplay );
m_DifficultyDisplay.SetName( "DifficultyDisplay" );
m_DifficultyDisplay.SetShadowLength( 0 );
SET_XY( m_DifficultyDisplay );
this->AddChild( &m_DifficultyDisplay );
{
CStringArray StageTexts;
GAMESTATE->GetAllStageTexts( StageTexts );
for( unsigned i = 0; i < StageTexts.size(); ++i )
{
CString path = THEME->GetPathG( m_sName, "stage "+StageTexts[i], true );
if( path != "" )
TEXTUREMAN->CacheTexture( path );
}
}
// loaded in AfterMusicChange
m_sprStage.SetName( "Stage" );
SET_XY( m_sprStage );
this->AddChild( &m_sprStage );
m_sprCDTitleFront.SetName( "CDTitle" );
m_sprCDTitleFront.Load( THEME->GetPathG(m_sName,"fallback cdtitle") );
SET_XY( m_sprCDTitleFront );
COMMAND( m_sprCDTitleFront, "Front" );
this->AddChild( &m_sprCDTitleFront );
m_sprCDTitleBack.SetName( "CDTitle" );
m_sprCDTitleBack.Load( THEME->GetPathG(m_sName,"fallback cdtitle") );
SET_XY( m_sprCDTitleBack );
COMMAND( m_sprCDTitleBack, "Back" );
this->AddChild( &m_sprCDTitleBack );
m_GrooveRadar.SetName( "Radar" );
SET_XY( m_GrooveRadar );
if( SHOW_RADAR )
this->AddChild( &m_GrooveRadar );
m_GrooveGraph.SetName( "Graph" );
SET_XY( m_GrooveGraph );
if( SHOW_GRAPH )
this->AddChild( &m_GrooveGraph );
m_textSongOptions.SetName( "SongOptions" );
m_textSongOptions.LoadFromFont( THEME->GetPathToF("Common normal") );
SET_XY( m_textSongOptions );
this->AddChild( &m_textSongOptions );
m_textNumSongs.SetName( "NumSongs" );
m_textNumSongs.LoadFromFont( THEME->GetPathF(m_sName,"num songs") );
SET_XY( m_textNumSongs );
this->AddChild( &m_textNumSongs );
m_textTotalTime.SetName( "TotalTime" );
m_textTotalTime.LoadFromFont( THEME->GetPathF(m_sName,"total time") );
SET_XY( m_textTotalTime );
this->AddChild( &m_textTotalTime );
m_CourseContentsFrame.SetName( "CourseContents" );
SET_XY( m_CourseContentsFrame );
this->AddChild( &m_CourseContentsFrame );
m_Artist.SetName( "ArtistDisplay" );
m_Artist.Load();
SET_XY( m_Artist );
this->AddChild( &m_Artist );
m_MachineRank.SetName( "MachineRank" );
m_MachineRank.LoadFromFont( THEME->GetPathF(m_sName,"rank") );
SET_XY( m_MachineRank );
this->AddChild( &m_MachineRank );
if( SHOW_DIFFICULTY_LIST )
{
m_DifficultyList.SetName( "DifficultyList" );
m_DifficultyList.Load();
SET_XY_AND_ON_COMMAND( m_DifficultyList );
this->AddChild( &m_DifficultyList );
}
FOREACH_HumanPlayer( p )
{
m_sprDifficultyFrame[p].SetName( ssprintf("DifficultyFrameP%d",p+1) );
m_sprDifficultyFrame[p].Load( THEME->GetPathG(m_sName,ssprintf("difficulty frame p%d",p+1)) );
m_sprDifficultyFrame[p].StopAnimating();
SET_XY( m_sprDifficultyFrame[p] );
this->AddChild( &m_sprDifficultyFrame[p] );
m_DifficultyIcon[p].SetName( ssprintf("DifficultyIconP%d",p+1) );
m_DifficultyIcon[p].Load( THEME->GetPathG(m_sName,ssprintf("difficulty icons 1x%d",NUM_DIFFICULTIES)) );
SET_XY( m_DifficultyIcon[p] );
this->AddChild( &m_DifficultyIcon[p] );
m_AutoGenIcon[p].SetName( ssprintf("AutogenIconP%d",p+1) );
m_AutoGenIcon[p].Load( THEME->GetPathG(m_sName,"autogen") );
SET_XY( m_AutoGenIcon[p] );
this->AddChild( &m_AutoGenIcon[p] );
m_OptionIconRow[p].SetName( ssprintf("OptionIconsP%d",p+1) );
m_OptionIconRow[p].Load( (PlayerNumber)p );
m_OptionIconRow[p].Refresh();
SET_XY( m_OptionIconRow[p] );
this->AddChild( &m_OptionIconRow[p] );
m_sprMeterFrame[p].SetName( ssprintf("MeterFrameP%d",p+1) );
m_sprMeterFrame[p].Load( THEME->GetPathG(m_sName,ssprintf("meter frame p%d",p+1)) );
SET_XY( m_sprMeterFrame[p] );
this->AddChild( &m_sprMeterFrame[p] );
if( SHOW_PANES )
{
m_PaneDisplay[p].SetName( "PaneDisplay", ssprintf("PaneDisplayP%d",p+1) );
m_PaneDisplay[p].Load( (PlayerNumber) p );
this->AddChild( &m_PaneDisplay[p] );
}
m_DifficultyMeter[p].SetName( "DifficultyMeter", ssprintf("MeterP%d",p+1) );
m_DifficultyMeter[p].Load();
SET_XY_AND_ON_COMMAND( m_DifficultyMeter[p] );
this->AddChild( &m_DifficultyMeter[p] );
// add an icon onto the song select to show what
// character they're using.
m_sprHighScoreFrame[p].SetName( ssprintf("ScoreFrameP%d",p+1) );
m_sprHighScoreFrame[p].Load( THEME->GetPathG(m_sName,ssprintf("score frame p%d",p+1)) );
SET_XY( m_sprHighScoreFrame[p] );
this->AddChild( &m_sprHighScoreFrame[p] );
m_textHighScore[p].SetName( ssprintf("ScoreP%d",p+1) );
m_textHighScore[p].LoadFromFont( THEME->GetPathF(m_sName,"score") );
m_textHighScore[p].SetShadowLength( 0 );
m_textHighScore[p].SetDiffuse( PlayerToColor(p) );
SET_XY( m_textHighScore[p] );
this->AddChild( &m_textHighScore[p] );
}
m_MusicSortDisplay.SetName( "SortIcon" );
m_MusicSortDisplay.Set( GAMESTATE->m_SortOrder );
SET_XY( m_MusicSortDisplay );
this->AddChild( &m_MusicSortDisplay );
m_sprBalloon.SetName( "Balloon" );
TEXTUREMAN->CacheTexture( THEME->GetPathG(m_sName,"balloon long") );
TEXTUREMAN->CacheTexture( THEME->GetPathG(m_sName,"balloon marathon") );
this->AddChild( &m_sprBalloon );
m_sprCourseHasMods.LoadAndSetName( m_sName, "CourseHasMods" );
SET_XY( m_sprCourseHasMods );
this->AddChild( m_sprCourseHasMods );
m_sprOptionsMessage.SetName( "OptionsMessage" );
m_sprOptionsMessage.Load( THEME->GetPathG(m_sName,"options message 1x2") );
m_sprOptionsMessage.StopAnimating();
m_sprOptionsMessage.SetDiffuse( RageColor(1,1,1,0) ); // invisible
//this->AddChild( &m_sprOptionsMessage ); // we have to draw this manually over the top of transitions
FOREACH_PlayerNumber( p )
{
m_sprNonPresence[p].SetName( ssprintf("NonPresenceP%d",p+1) );
m_sprNonPresence[p].Load( THEME->GetPathG(m_sName,ssprintf("nonpresence p%d",p+1)) );
SET_XY( m_sprNonPresence[p] );
this->AddChild( &m_sprNonPresence[p] );
}
m_Overlay.SetName( "Overlay");
m_Overlay.LoadFromAniDir( THEME->GetPathB(m_sName, "overlay"));
this->AddChild( &m_Overlay );
m_bgOptionsOut.Load( THEME->GetPathToB(m_sName+" options out") );
// this->AddChild( &m_bgOptionsOut ); // drawn on top
m_bgNoOptionsOut.Load( THEME->GetPathToB(m_sName+" no options out") );
// this->AddChild( &m_bgNoOptionsOut ); // drawn on top
m_soundDifficultyEasier.Load( THEME->GetPathS(m_sName,"difficulty easier") );
m_soundDifficultyHarder.Load( THEME->GetPathS(m_sName,"difficulty harder") );
m_soundOptionsChange.Load( THEME->GetPathS(m_sName,"options") );
m_soundLocked.Load( THEME->GetPathS(m_sName,"locked") );
SOUND->PlayOnceFromAnnouncer( "select music intro" );
m_bMadeChoice = false;
m_bGoToOptions = false;
m_bAllowOptionsMenu = m_bAllowOptionsMenuRepeat = false;
UpdateOptionsDisplays();
AfterMusicChange();
TweenOnScreen();
this->SortByDrawOrder();
}
ScreenSelectMusic::~ScreenSelectMusic()
{
LOG->Trace( "ScreenSelectMusic::~ScreenSelectMusic()" );
}
void ScreenSelectMusic::DrawPrimitives()
{
DISPLAY->CameraPushMatrix();
DISPLAY->LoadMenuPerspective( FOV, FOV_CENTER_X, FOV_CENTER_Y );
Screen::DrawPrimitives();
m_sprOptionsMessage.Draw();
m_bgOptionsOut.Draw();
m_bgNoOptionsOut.Draw();
DISPLAY->CameraPopMatrix();
}
void ScreenSelectMusic::TweenSongPartsOnScreen( bool Initial )
{
m_GrooveRadar.StopTweening();
m_GrooveGraph.StopTweening();
m_GrooveRadar.TweenOnScreen();
m_GrooveGraph.TweenOnScreen();
if( SHOW_DIFFICULTY_LIST )
{
if( Initial )
{
ON_COMMAND( m_DifficultyList );
m_DifficultyList.TweenOnScreen();
}
// else // do this after SM_SortOrderChanged
// m_DifficultyList.Show();
}
{
FOREACH_HumanPlayer( p )
{
ON_COMMAND( m_sprDifficultyFrame[p] );
ON_COMMAND( m_sprMeterFrame[p] );
ON_COMMAND( m_DifficultyIcon[p] );
ON_COMMAND( m_AutoGenIcon[p] );
}
}
{
FOREACH_PlayerNumber( p )
{
ON_COMMAND( m_sprNonPresence[p] );
}
}
}
void ScreenSelectMusic::TweenSongPartsOffScreen( bool Final )
{
m_GrooveRadar.TweenOffScreen();
m_GrooveGraph.TweenOffScreen();
if( SHOW_DIFFICULTY_LIST )
{
if( Final )
{
OFF_COMMAND( m_DifficultyList );
m_DifficultyList.TweenOffScreen();
}
else
m_DifficultyList.Hide();
}
{
FOREACH_HumanPlayer( p )
{
OFF_COMMAND( m_sprDifficultyFrame[p] );
OFF_COMMAND( m_sprMeterFrame[p] );
OFF_COMMAND( m_DifficultyIcon[p] );
OFF_COMMAND( m_AutoGenIcon[p] );
}
}
{
FOREACH_PlayerNumber( p )
{
OFF_COMMAND( m_sprNonPresence[p] );
}
}
}
void ScreenSelectMusic::TweenCoursePartsOnScreen( bool Initial )
{
m_CourseContentsFrame.SetZoomY( 1 );
if( Initial )
{
COMMAND( m_CourseContentsFrame, "On" );
}
else
{
m_CourseContentsFrame.SetFromGameState();
COMMAND( m_CourseContentsFrame, "Show" );
}
}
void ScreenSelectMusic::TweenCoursePartsOffScreen( bool Final )
{
if( Final )
{
OFF_COMMAND( m_CourseContentsFrame );
}
else
{
COMMAND( m_CourseContentsFrame, "Hide" );
}
}
void ScreenSelectMusic::SkipSongPartTweens()
{
m_GrooveRadar.FinishTweening();
m_GrooveGraph.FinishTweening();
if( SHOW_DIFFICULTY_LIST )
m_DifficultyList.FinishTweening();
FOREACH_PlayerNumber( p )
{
if( !GAMESTATE->IsHumanPlayer(p) )
continue; // skip
m_sprDifficultyFrame[p].FinishTweening();
m_sprMeterFrame[p].FinishTweening();
m_DifficultyIcon[p].FinishTweening();
m_AutoGenIcon[p].FinishTweening();
}
}
void ScreenSelectMusic::SkipCoursePartTweens()
{
m_CourseContentsFrame.FinishTweening();
}
void ScreenSelectMusic::TweenOnScreen()
{
TweenSongPartsOnScreen( true );
TweenCoursePartsOnScreen( true );
switch( GAMESTATE->m_SortOrder )
{
case SORT_ALL_COURSES:
case SORT_NONSTOP_COURSES:
case SORT_ONI_COURSES:
case SORT_ENDLESS_COURSES:
TweenSongPartsOffScreen( false );
SkipSongPartTweens();
break;
default:
TweenCoursePartsOffScreen( false );
SkipCoursePartTweens();
break;
}
ON_COMMAND( m_sprBannerMask );
ON_COMMAND( m_Banner );
ON_COMMAND( m_sprBannerFrame );
ON_COMMAND( m_sprExplanation );
ON_COMMAND( m_BPMDisplay );
ON_COMMAND( m_DifficultyDisplay );
ON_COMMAND( m_sprStage );
ON_COMMAND( m_sprCDTitleFront );
ON_COMMAND( m_sprCDTitleBack );
ON_COMMAND( m_GrooveRadar );
ON_COMMAND( m_GrooveGraph );
ON_COMMAND( m_textSongOptions );
ON_COMMAND( m_textNumSongs );
ON_COMMAND( m_textTotalTime );
ON_COMMAND( m_MusicSortDisplay );
ON_COMMAND( m_MusicWheelUnder );
m_MusicWheel.TweenOnScreen();
ON_COMMAND( m_sprBalloon );
ON_COMMAND( m_sprCourseHasMods );
ON_COMMAND( m_MusicWheel );
ON_COMMAND( m_Artist );
ON_COMMAND( m_MachineRank );
ON_COMMAND( m_Overlay );
FOREACH_HumanPlayer( p )
{
ON_COMMAND( m_sprCharacterIcon[p] );
ON_COMMAND( m_OptionIconRow[p] );
ON_COMMAND( m_sprHighScoreFrame[p] );
ON_COMMAND( m_textHighScore[p] );
if( SHOW_PANES )
ON_COMMAND( m_PaneDisplay[p] );
ON_COMMAND( m_DifficultyMeter[p] );
}
if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() )
m_textSongOptions.Command( SONG_OPTIONS_EXTRA_COMMAND );
}
void ScreenSelectMusic::TweenOffScreen()
{
switch( GAMESTATE->m_SortOrder )
{
case SORT_ALL_COURSES:
case SORT_NONSTOP_COURSES:
case SORT_ONI_COURSES:
case SORT_ENDLESS_COURSES:
TweenCoursePartsOffScreen( true );
break;
default:
TweenSongPartsOffScreen( true );
break;
}
OFF_COMMAND( m_sprBannerMask );
OFF_COMMAND( m_Banner );
OFF_COMMAND( m_sprBannerFrame );
OFF_COMMAND( m_sprExplanation );
OFF_COMMAND( m_BPMDisplay );
OFF_COMMAND( m_DifficultyDisplay );
OFF_COMMAND( m_sprStage );
OFF_COMMAND( m_sprCDTitleFront );
OFF_COMMAND( m_sprCDTitleBack );
OFF_COMMAND( m_GrooveRadar );
OFF_COMMAND( m_GrooveGraph );
OFF_COMMAND( m_textSongOptions );
OFF_COMMAND( m_textNumSongs );
OFF_COMMAND( m_textTotalTime );
OFF_COMMAND( m_MusicSortDisplay );
m_MusicWheel.TweenOffScreen();
OFF_COMMAND( m_MusicWheelUnder );
OFF_COMMAND( m_MusicWheel );
OFF_COMMAND( m_sprBalloon );
OFF_COMMAND( m_sprCourseHasMods );
OFF_COMMAND( m_Artist );
OFF_COMMAND( m_MachineRank );
OFF_COMMAND( m_Overlay );
FOREACH_HumanPlayer( p )
{
OFF_COMMAND(m_sprCharacterIcon[p]);
OFF_COMMAND( m_OptionIconRow[p] );
OFF_COMMAND( m_sprHighScoreFrame[p] );
OFF_COMMAND( m_textHighScore[p] );
if( SHOW_PANES )
OFF_COMMAND( m_PaneDisplay[p] );
OFF_COMMAND( m_DifficultyMeter[p] );
}
}
/* This hides elements that are only relevant when displaying a single song,
* and shows elements for course display. XXX: Allow different tween commands. */
void ScreenSelectMusic::SwitchDisplayMode( DisplayMode dm )
{
if( m_DisplayMode == dm )
return;
// tween off
switch( m_DisplayMode )
{
case DISPLAY_SONGS:
TweenSongPartsOffScreen( false );
break;
case DISPLAY_COURSES:
TweenCoursePartsOffScreen( false );
break;
case DISPLAY_MODES:
break;
}
// tween on
m_DisplayMode = dm;
switch( m_DisplayMode )
{
case DISPLAY_SONGS:
TweenSongPartsOnScreen( false );
break;
case DISPLAY_COURSES:
TweenCoursePartsOnScreen( false );
break;
case DISPLAY_MODES:
break;
}
}
void ScreenSelectMusic::TweenScoreOnAndOffAfterChangeSort()
{
FOREACH_PlayerNumber( p )
{
if( !GAMESTATE->IsHumanPlayer(p) )
continue; // skip
m_textHighScore[p].Command( SCORE_SORT_CHANGE_COMMAND(p) );
m_sprHighScoreFrame[p].Command( SCORE_FRAME_SORT_CHANGE_COMMAND(p) );
}
switch( GAMESTATE->m_SortOrder )
{
case SORT_ALL_COURSES:
case SORT_NONSTOP_COURSES:
case SORT_ONI_COURSES:
case SORT_ENDLESS_COURSES:
SwitchDisplayMode( DISPLAY_COURSES );
break;
case SORT_SORT_MENU:
case SORT_MODE_MENU:
SwitchDisplayMode( DISPLAY_MODES );
break;
default:
SwitchDisplayMode( DISPLAY_SONGS );
break;
}
}
void ScreenSelectMusic::CheckBackgroundRequests()
{
if( g_bCDTitleWaiting )
{
/* The CDTitle is normally very small, so we don't bother waiting to display it. */
CString sPath;
if( m_BackgroundLoader.IsCacheFileFinished(g_sCDTitlePath, sPath) )
{
g_bCDTitleWaiting = false;
CString sCDTitlePath = sPath;
if( sCDTitlePath.empty() || !IsAFile(sCDTitlePath) )
sCDTitlePath = g_bWantFallbackCdTitle? g_sFallbackCDTitlePath:"";
if( !sCDTitlePath.empty() )
{
TEXTUREMAN->DisableOddDimensionWarning();
m_sprCDTitleFront.Load( sCDTitlePath );
m_sprCDTitleBack.Load( sCDTitlePath );
TEXTUREMAN->EnableOddDimensionWarning();
}
m_BackgroundLoader.FinishedWithCachedFile( g_sCDTitlePath );
}
else
return;
}
/* Loading the rest can cause small skips, so don't do it until the wheel settles. */
if( !m_MusicWheel.IsSettled() )
return;
if( g_bBannerWaiting )
{
float HighQualTime = THEME->GetMetricF("FadingBanner","FadeSeconds");
CString sPath;
if( g_StartedLoadingAt.Ago() >= HighQualTime && m_BackgroundLoader.IsCacheFileFinished(g_sBannerPath, sPath) )
{
g_bBannerWaiting = false;
RageTimer foo;
m_Banner.Load( sPath );
m_BackgroundLoader.FinishedWithCachedFile( g_sBannerPath );
}
else
return;
}
/* Nothing else is going. Start the music, if we havn't yet. */
if( g_bSampleMusicWaiting )
{
/* Don't start the music sample when moving fast. */
if( g_StartedLoadingAt.Ago() >= SAMPLE_MUSIC_DELAY )
{
g_bSampleMusicWaiting = false;
SOUND->PlayMusic(
m_sSampleMusicToPlay, m_pSampleMusicTimingData,
true, m_fSampleStartSeconds, m_fSampleLengthSeconds,
1.5f, /* fade out for 1.5 seconds */
ALIGN_MUSIC_BEATS );
}
else
return;
}
}
void ScreenSelectMusic::Update( float fDeltaTime )
{
Screen::Update( fDeltaTime );
m_bgOptionsOut.Update( fDeltaTime );
m_bgNoOptionsOut.Update( fDeltaTime );
m_sprOptionsMessage.Update( fDeltaTime );
CheckBackgroundRequests();
}
void ScreenSelectMusic::Input( const DeviceInput& DeviceI, InputEventType type, const GameInput &GameI, const MenuInput &MenuI, const StyleInput &StyleI )
{
// LOG->Trace( "ScreenSelectMusic::Input()" );
// debugging?
// I just like being able to see untransliterated titles occasionally.
if( DeviceI.device == DEVICE_KEYBOARD && DeviceI.button == SDLK_F9 )
{
if( type != IET_FIRST_PRESS ) return;
PREFSMAN->m_bShowNative ^= 1;
m_MusicWheel.RebuildMusicWheelItems();
m_CourseContentsFrame.SetFromGameState();
return;
}
if( !GameI.IsValid() ) return; // don't care
/* XXX: What's the difference between this and StyleI.player? */
/* StyleI won't be valid if it's a menu button that's pressed.
* There's got to be a better way of doing this. -Chris */
PlayerNumber pn = GAMESTATE->GetCurrentStyle()->ControllerToPlayerNumber( GameI.controller );
if( !GAMESTATE->IsHumanPlayer(pn) )
return;
if( m_bMadeChoice &&
MenuI.IsValid() &&
MenuI.button == MENU_BUTTON_START &&
type != IET_RELEASE &&
IsTransitioning() &&
!GAMESTATE->IsExtraStage() &&
!GAMESTATE->IsExtraStage2() )
{
if(m_bGoToOptions) return; /* got it already */
if(!m_bAllowOptionsMenu) return; /* not allowed */
if( !m_bAllowOptionsMenuRepeat &&
(type == IET_SLOW_REPEAT || type == IET_FAST_REPEAT ))
return; /* not allowed yet */
m_bGoToOptions = true;
m_sprOptionsMessage.SetState( 1 );
SCREENMAN->PlayStartSound();
return;
}
if( IsTransitioning() )
return; // ignore
if( m_bMadeChoice ) return; // ignore
if( MenuI.button == MENU_BUTTON_RIGHT || MenuI.button == MENU_BUTTON_LEFT )
{
/* If we're rouletting, hands off. */
if(m_MusicWheel.IsRouletting())
return;
// TRICKY: There's lots of weirdness that can happen here when tapping
// Left and Right quickly, like when changing sort.
bool bLeftPressed = INPUTMAPPER->IsButtonDown( MenuInput(MenuI.player, MENU_BUTTON_LEFT) );
bool bRightPressed = INPUTMAPPER->IsButtonDown( MenuInput(MenuI.player, MENU_BUTTON_RIGHT) );
bool bLeftAndRightPressed = bLeftPressed && bRightPressed;
bool bLeftOrRightPressed = bLeftPressed || bRightPressed;
switch( type )
{
case IET_RELEASE:
// when a key is released, stop moving the wheel
if( !bLeftOrRightPressed )
m_MusicWheel.Move( 0 );
// Reset the repeat timer when a key is released.
// This fixes jumping when you release Left and Right at the same
// time (e.g. after tapping Left+Right to change sort).
INPUTMAPPER->ResetKeyRepeat( MenuInput(MenuI.player, MENU_BUTTON_LEFT) );
INPUTMAPPER->ResetKeyRepeat( MenuInput(MenuI.player, MENU_BUTTON_RIGHT) );
break;
case IET_FIRST_PRESS:
if( MenuI.button == MENU_BUTTON_RIGHT )
m_MusicWheel.Move( +1 );
else
m_MusicWheel.Move( -1 );
// The wheel moves faster than one item between FIRST_PRESS
// and SLOW_REPEAT. Stop the wheel immediately after moving one
// item if both Left and Right are held. This way, we won't move
// another item
if( bLeftAndRightPressed )
m_MusicWheel.Move( 0 );
break;
case IET_SLOW_REPEAT:
case IET_FAST_REPEAT:
// We need to handle the repeat events to start the wheel spinning again
// when Left and Right are being held, then one is released.
if( bLeftAndRightPressed )
{
// Don't spin if holding both buttons
m_MusicWheel.Move( 0 );
}
else
{
if( MenuI.button == MENU_BUTTON_RIGHT )
m_MusicWheel.Move( +1 );
else
m_MusicWheel.Move( -1 );
}
break;
}
}
// TRICKY: Do default processing of MenuLeft and MenuRight before detecting
// codes. Do default processing of Start AFTER detecting codes. This gives us a
// change to return if Start is part of a code because we don't want to process
// Start as "move to the next screen" if it was just part of a code.
switch( MenuI.button )
{
case MENU_BUTTON_UP: this->MenuUp( MenuI.player, type ); break;
case MENU_BUTTON_DOWN: this->MenuDown( MenuI.player, type ); break;
case MENU_BUTTON_LEFT: this->MenuLeft( MenuI.player, type ); break;
case MENU_BUTTON_RIGHT: this->MenuRight( MenuI.player, type ); break;
case MENU_BUTTON_BACK:
/* Don't make the user hold the back button if they're pressing escape and escape is the back button. */
if( DeviceI.device == DEVICE_KEYBOARD && DeviceI.button == SDLK_ESCAPE )
this->MenuBack( MenuI.player );
else
Screen::MenuBack( MenuI.player, type );
break;
// Do the default handler for Start after detecting codes.
// case MENU_BUTTON_START: this->MenuStart( MenuI.player, type ); break;
case MENU_BUTTON_COIN: this->MenuCoin( MenuI.player, type ); break;
}
if( type == IET_FIRST_PRESS )
{
if( CodeDetector::EnteredEasierDifficulty(GameI.controller) )
{
if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() )
m_soundLocked.Play();
else
ChangeDifficulty( pn, -1 );
return;
}
if( CodeDetector::EnteredHarderDifficulty(GameI.controller) )
{
if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() )
m_soundLocked.Play();
else
ChangeDifficulty( pn, +1 );
return;
}
if( CodeDetector::EnteredSortMenu(GameI.controller) )
{
/* Ignore the SortMenu when in course mode. However, still check for the code, so
* if people try pressing left+right+start in course mode, we don't pick the selected
* course on them. */
if( GAMESTATE->IsCourseMode() )
; /* nothing */
else if( ( GAMESTATE->IsExtraStage() && !PREFSMAN->m_bPickExtraStage ) || GAMESTATE->IsExtraStage2() )
m_soundLocked.Play();
else
m_MusicWheel.ChangeSort( SORT_SORT_MENU );
return;
}
if( CodeDetector::EnteredModeMenu(GameI.controller) )
{
if( ( GAMESTATE->IsExtraStage() && !PREFSMAN->m_bPickExtraStage ) || GAMESTATE->IsExtraStage2() )
m_soundLocked.Play();
else
m_MusicWheel.ChangeSort( SORT_MODE_MENU );
return;
}
if( CodeDetector::EnteredNextSort(GameI.controller) )
{
if( ( GAMESTATE->IsExtraStage() && !PREFSMAN->m_bPickExtraStage ) || GAMESTATE->IsExtraStage2() )
m_soundLocked.Play();
else
m_MusicWheel.NextSort();
return;
}
if( !GAMESTATE->IsExtraStage() && !GAMESTATE->IsExtraStage2() && CodeDetector::DetectAndAdjustMusicOptions(GameI.controller) )
{
m_soundOptionsChange.Play();
UpdateOptionsDisplays();
return;
}
}
switch( MenuI.button )
{
case MENU_BUTTON_START: Screen::MenuStart( MenuI.player, type ); break;
}
}
void ScreenSelectMusic::ChangeDifficulty( PlayerNumber pn, int dir )
{
LOG->Trace( "ScreenSelectMusic::ChangeDifficulty( %d, %d )", pn, dir );
ASSERT( GAMESTATE->IsHumanPlayer(pn) );
switch( m_MusicWheel.GetSelectedType() )
{
case TYPE_SONG:
case TYPE_PORTAL:
{
m_iSelection[pn] += dir;
if( CLAMP(m_iSelection[pn],0,m_vpSteps.size()-1) )
return;
// the user explicity switched difficulties. Update the preferred difficulty
GAMESTATE->ChangePreferredDifficulty( pn, m_vpSteps[ m_iSelection[pn] ]->GetDifficulty() );
if( dir < 0 )
m_soundDifficultyEasier.Play();
else
m_soundDifficultyHarder.Play();
FOREACH_HumanPlayer( p )
{
if( pn == p || GAMESTATE->DifficultiesLocked() )
{
m_iSelection[p] = m_iSelection[pn];
AfterStepsChange( p );
}
}
}
break;
case TYPE_COURSE:
{
m_iSelection[pn] += dir;
if( CLAMP(m_iSelection[pn],0,m_vpTrails.size()-1) )
return;
// the user explicity switched difficulties. Update the preferred difficulty
GAMESTATE->ChangePreferredCourseDifficulty( pn, m_vpTrails[ m_iSelection[pn] ]->m_CourseDifficulty );
if( dir < 0 )
m_soundDifficultyEasier.Play();
else
m_soundDifficultyHarder.Play();
FOREACH_HumanPlayer( p )
{
if( pn == p || GAMESTATE->DifficultiesLocked() )
{
m_iSelection[p] = m_iSelection[pn];
AfterTrailChange( p );
}
}
}
break;
case TYPE_RANDOM:
case TYPE_ROULETTE:
/* XXX: We could be on a music or course sort, or even one with both; we don't
* really know which difficulty to change. Maybe the two difficulties should be
* linked ... */
if( GAMESTATE->ChangePreferredDifficulty( pn, dir ) )
{
if( dir < 0 )
m_soundDifficultyEasier.Play();
else
m_soundDifficultyHarder.Play();
AfterMusicChange();
FOREACH_HumanPlayer( p )
{
if( pn == p || GAMESTATE->DifficultiesLocked() )
{
m_iSelection[p] = m_iSelection[pn];
AfterStepsChange( p );
}
}
}
break;
}
}
void ScreenSelectMusic::HandleScreenMessage( const ScreenMessage SM )
{
switch( SM )
{
case SM_AllowOptionsMenuRepeat:
m_bAllowOptionsMenuRepeat = true;
break;
case SM_MenuTimer:
if( m_MusicWheel.IsRouletting() )
{
MenuStart(PLAYER_INVALID);
m_MenuTimer->SetSeconds( 15 );
m_MenuTimer->Start();
}
else if( DO_ROULETTE_ON_MENU_TIMER )
{
if( m_MusicWheel.GetSelectedType() != TYPE_SONG )
{
m_MusicWheel.StartRoulette();
m_MenuTimer->SetSeconds( 15 );
m_MenuTimer->Start();
}
else
{
MenuStart(PLAYER_INVALID);
}
}
else
{
if( m_MusicWheel.GetSelectedType() != TYPE_SONG && m_MusicWheel.GetSelectedType() != TYPE_COURSE )
m_MusicWheel.StartRandom();
MenuStart(PLAYER_INVALID);
}
return;
case SM_GoToPrevScreen:
SCREENMAN->SetNewScreen( PREV_SCREEN );
/* We may have stray SM_SongChanged messages from the music wheel. We can't
* handle them anymore, since the title menu (and attract screens) reset
* the game state, so just discard them. */
ClearMessageQueue();
return;
case SM_BeginFadingOut:
/* XXX: yuck. Later on, maybe this can be done in one BGA with lua ... */
if( m_bGoToOptions )
m_bgOptionsOut.StartTransitioning( SM_GoToNextScreen );
else
m_bgNoOptionsOut.StartTransitioning( SM_GoToNextScreen );
break;
case SM_GoToNextScreen:
if( m_bGoToOptions )
{
SCREENMAN->SetNewScreen( NEXT_OPTIONS_SCREEN );
}
else
{
GAMESTATE->AdjustFailType();
SOUND->StopMusic();
SCREENMAN->SetNewScreen( NEXT_SCREEN );
}
return;
case SM_SongChanged:
AfterMusicChange();
break;
case SM_SortOrderChanging: /* happens immediately */
// m_MusicSortDisplay.FadeOff( 0, "fade", TWEEN_TIME );
TweenScoreOnAndOffAfterChangeSort();
break;
case SM_SortOrderChanged: /* happens after the wheel is off and the new song is selected */
SortOrderChanged();
break;
case SM_GainFocus:
CodeDetector::RefreshCacheItems( CODES );
break;
case SM_LoseFocus:
CodeDetector::RefreshCacheItems(); /* reset for other screens */
break;
}
Screen::HandleScreenMessage( SM );
}
void ScreenSelectMusic::MenuStart( PlayerNumber pn )
{
// this needs to check whether valid Steps are selected!
bool bResult = m_MusicWheel.Select();
/* If false, we don't have a selection just yet. */
if( !bResult )
return;
// a song was selected
switch( m_MusicWheel.GetSelectedType() )
{
case TYPE_SONG:
case TYPE_PORTAL:
{
const bool bIsNew = PROFILEMAN->IsSongNew( m_MusicWheel.GetSelectedSong() );
bool bIsHard = false;
FOREACH_HumanPlayer( p )
{
if( GAMESTATE->m_pCurSteps[p] && GAMESTATE->m_pCurSteps[p]->GetMeter() >= 10 )
bIsHard = true;
}
/* See if this song is a repeat. If we're in event mode, only check the last five songs. */
bool bIsRepeat = false;
int i = 0;
if( PREFSMAN->m_bEventMode )
i = max( 0, int(g_vPlayedStageStats.size())-5 );
for( ; i < (int)g_vPlayedStageStats.size(); ++i )
if( g_vPlayedStageStats[i].vpSongs.back() == m_MusicWheel.GetSelectedSong() )
bIsRepeat = true;
/* Don't complain about repeats if the user didn't get to pick. */
if( GAMESTATE->IsExtraStage() && !PREFSMAN->m_bPickExtraStage )
bIsRepeat = false;
if( bIsRepeat )
SOUND->PlayOnceFromAnnouncer( "select music comment repeat" );
else if( bIsNew )
SOUND->PlayOnceFromAnnouncer( "select music comment new" );
else if( bIsHard )
SOUND->PlayOnceFromAnnouncer( "select music comment hard" );
else
SOUND->PlayOnceFromAnnouncer( "select music comment general" );
m_bMadeChoice = true;
/* If we're in event mode, we may have just played a course (putting us
* in course mode). Make sure we're in a single song mode. */
if( GAMESTATE->IsCourseMode() )
GAMESTATE->m_PlayMode = PLAY_MODE_REGULAR;
}
break;
case TYPE_COURSE:
{
SOUND->PlayOnceFromAnnouncer( "select course comment general" );
Course *pCourse = m_MusicWheel.GetSelectedCourse();
ASSERT( pCourse );
GAMESTATE->m_PlayMode = pCourse->GetPlayMode();
// apply #LIVES
if( pCourse->m_iLives != -1 )
{
GAMESTATE->m_SongOptions.m_LifeType = SongOptions::LIFE_BATTERY;
GAMESTATE->m_SongOptions.m_iBatteryLives = pCourse->m_iLives;
}
m_bMadeChoice = true;
}
break;
case TYPE_SECTION:
case TYPE_ROULETTE:
case TYPE_RANDOM:
case TYPE_SORT:
break;
default:
ASSERT(0);
}
if( m_bMadeChoice )
{
TweenOffScreen();
SCREENMAN->PlayStartSound();
if( !GAMESTATE->IsExtraStage() && !GAMESTATE->IsExtraStage2() )
{
// float fShowSeconds = m_Out.GetLengthSeconds();
// show "hold START for options"
m_sprOptionsMessage.SetDiffuse( RageColor(1,1,1,1) ); // visible
SET_XY_AND_ON_COMMAND( m_sprOptionsMessage );
m_bAllowOptionsMenu = true;
/* Don't accept a held START for a little while, so it's not
* hit accidentally. Accept an initial START right away, though,
* so we don't ignore deliberate fast presses (which would be
* annoying). */
this->PostScreenMessage( SM_AllowOptionsMenuRepeat, 0.5f );
}
StartTransitioning( SM_BeginFadingOut );
}
if( GAMESTATE->IsExtraStage() && PREFSMAN->m_bPickExtraStage )
{
/* Check if user selected the real extra stage. */
Song* pSong;
Steps* pSteps;
PlayerOptions po;
SongOptions so;
SONGMAN->GetExtraStageInfo( false, GAMESTATE->GetCurrentStyle(), pSong, pSteps, po, so );
ASSERT(pSong);
/* Enable 2nd extra stage if user chose the correct song */
if( m_MusicWheel.GetSelectedSong() == pSong )
GAMESTATE->m_bAllow2ndExtraStage = true;
else
GAMESTATE->m_bAllow2ndExtraStage = false;
}
}
void ScreenSelectMusic::MenuBack( PlayerNumber pn )
{
SOUND->StopMusic();
Back( SM_GoToPrevScreen );
}
void ScreenSelectMusic::AfterStepsChange( PlayerNumber pn )
{
ASSERT( GAMESTATE->IsHumanPlayer(pn) );
CLAMP( m_iSelection[pn], 0, m_vpSteps.size()-1 );
Song* pSong = GAMESTATE->m_pCurSong;
Steps* pSteps = m_vpSteps.empty()? NULL: m_vpSteps[m_iSelection[pn]];
GAMESTATE->m_pCurSteps[pn] = pSteps;
int iScore = 0;
if( pSteps )
{
Profile* pProfile = PROFILEMAN->IsUsingProfile(pn) ? PROFILEMAN->GetProfile(pn) : PROFILEMAN->GetMachineProfile();
iScore = pProfile->GetStepsHighScoreList(pSong,pSteps).GetTopScore().iScore;
}
m_textHighScore[pn].SetText( ssprintf("%*i", NUM_SCORE_DIGITS, iScore) );
m_DifficultyIcon[pn].SetFromSteps( pn, pSteps );
if( pSteps && pSteps->IsAutogen() )
{
m_AutoGenIcon[pn].SetEffectDiffuseShift();
}
else
{
m_AutoGenIcon[pn].SetEffectNone();
m_AutoGenIcon[pn].SetDiffuse( RageColor(1,1,1,0) );
}
m_DifficultyMeter[pn].SetFromGameState( pn );
if( SHOW_DIFFICULTY_LIST )
m_DifficultyList.SetFromGameState();
m_GrooveRadar.SetFromSteps( pn, pSteps );
m_MusicWheel.NotesOrTrailChanged( pn );
if( SHOW_PANES )
m_PaneDisplay[pn].SetFromGameState();
}
void ScreenSelectMusic::AfterTrailChange( PlayerNumber pn )
{
ASSERT( GAMESTATE->IsHumanPlayer(pn) );
CLAMP( m_iSelection[pn], 0, m_vpTrails.size()-1 );
Course* pCourse = GAMESTATE->m_pCurCourse;
Trail* pTrail = m_vpTrails.empty()? NULL: m_vpTrails[m_iSelection[pn]];
GAMESTATE->m_pCurTrail[pn] = pTrail;
int iScore = 0;
if( pTrail )
{
Profile* pProfile = PROFILEMAN->IsUsingProfile(pn) ? PROFILEMAN->GetProfile(pn) : PROFILEMAN->GetMachineProfile();
iScore = pProfile->GetCourseHighScoreList(pCourse,pTrail).GetTopScore().iScore;
}
m_textHighScore[pn].SetText( ssprintf("%*i", NUM_SCORE_DIGITS, iScore) );
m_DifficultyIcon[pn].SetFromTrail( pn, pTrail );
//if( pTrail && pTrail->IsAutogen() )
//{
// m_AutoGenIcon[pn].SetEffectDiffuseShift();
//}
//else
//{
// m_AutoGenIcon[pn].SetEffectNone();
// m_AutoGenIcon[pn].SetDiffuse( RageColor(1,1,1,0) );
//}
/* Update the trail list, but don't actually start the tween; only do that when
* the actual course changes (AfterMusicChange). */
m_CourseContentsFrame.SetFromGameState();
// m_CourseContentsFrame.TweenInAfterChangedCourse();
m_DifficultyMeter[pn].SetFromGameState( pn );
if( SHOW_DIFFICULTY_LIST )
m_DifficultyList.SetFromGameState();
m_GrooveRadar.SetEmpty( pn );
m_MusicWheel.NotesOrTrailChanged( pn );
if( SHOW_PANES )
m_PaneDisplay[pn].SetFromGameState();
}
void ScreenSelectMusic::SwitchToPreferredDifficulty()
{
if( !GAMESTATE->m_pCurCourse )
{
FOREACH_HumanPlayer( pn )
{
/* Find the closest match to the user's preferred difficulty. */
int CurDifference = -1;
for( unsigned i=0; i<m_vpSteps.size(); i++ )
{
int Diff = abs(m_vpSteps[i]->GetDifficulty() - GAMESTATE->m_PreferredDifficulty[pn]);
if( CurDifference == -1 || Diff < CurDifference )
{
m_iSelection[pn] = i;
CurDifference = Diff;
}
}
CLAMP( m_iSelection[pn],0,m_vpSteps.size()-1 );
}
}
else
{
FOREACH_HumanPlayer( pn )
{
/* Find the closest match to the user's preferred difficulty. */
int CurDifference = -1;
for( unsigned i=0; i<m_vpTrails.size(); i++ )
{
int Diff = abs(m_vpTrails[i]->m_CourseDifficulty - GAMESTATE->m_PreferredCourseDifficulty[pn]);
if( CurDifference == -1 || Diff < CurDifference )
{
m_iSelection[pn] = i;
CurDifference = Diff;
}
}
CLAMP( m_iSelection[pn],0,m_vpTrails.size()-1 );
}
}
}
template<class T>
int FindCourseIndexOfSameMode( T begin, T end, const Course *p )
{
const PlayMode pm = p->GetPlayMode();
int n = 0;
for( T it = begin; it != end; ++it )
{
if( *it == p )
return n;
/* If it's not playable in this mode, don't increment. It might result in
* different output in different modes, but that's better than having holes. */
if( !(*it)->IsPlayableIn( GAMESTATE->GetCurrentStyle()->m_StepsType ) )
continue;
if( (*it)->GetPlayMode() != pm )
continue;
++n;
}
return -1;
}
void ScreenSelectMusic::AfterMusicChange()
{
if( !m_MusicWheel.IsRouletting() )
m_MenuTimer->Stall();
// lock difficulties. When switching from arcade to rave, we need to
// enforce that all players are at the same difficulty.
if( GAMESTATE->DifficultiesLocked() )
{
FOREACH_HumanPlayer( p )
{
m_iSelection[p] = m_iSelection[0];
GAMESTATE->m_PreferredDifficulty[p] = GAMESTATE->m_PreferredDifficulty[0];
}
}
Song* pSong = m_MusicWheel.GetSelectedSong();
GAMESTATE->m_pCurSong = pSong;
if( pSong )
GAMESTATE->m_pPreferredSong = pSong;
m_GrooveGraph.SetFromSong( pSong );
Course* pCourse = m_MusicWheel.GetSelectedCourse();
GAMESTATE->m_pCurCourse = pCourse;
if( pCourse )
GAMESTATE->m_pPreferredCourse = pCourse;
FOREACH_PlayerNumber( p )
{
GAMESTATE->m_pCurSteps[p] = NULL;
GAMESTATE->m_pCurTrail[p] = NULL;
m_vpSteps.clear();
m_vpTrails.clear();
}
m_Banner.SetMovingFast( !!m_MusicWheel.IsMoving() );
CString SampleMusicToPlay, SampleMusicTimingData;
vector<CString> m_Artists, m_AltArtists;
m_MachineRank.SetText( "" );
m_sSampleMusicToPlay = "";
m_pSampleMusicTimingData = NULL;
g_sCDTitlePath = "";
g_sBannerPath = "";
g_bWantFallbackCdTitle = false;
bool bWantBanner = true;
switch( m_MusicWheel.GetSelectedType() )
{
case TYPE_SECTION:
case TYPE_SORT:
{
CString sGroup = m_MusicWheel.GetSelectedSection();
FOREACH_PlayerNumber( p )
m_iSelection[p] = -1;
m_BPMDisplay.NoBPM();
g_sCDTitlePath = ""; // none
m_DifficultyDisplay.UnsetDifficulties();
m_fSampleStartSeconds = 0;
m_fSampleLengthSeconds = -1;
m_textNumSongs.SetText( "" );
m_textTotalTime.SetText( "" );
switch( m_MusicWheel.GetSelectedType() )
{
case TYPE_SECTION:
g_sBannerPath = SONGMAN->GetGroupBannerPath( sGroup );
m_sSampleMusicToPlay = THEME->GetPathS(m_sName,"section music");
break;
case TYPE_SORT:
bWantBanner = false; /* we load it ourself */
switch( GAMESTATE->m_SortOrder )
{
case SORT_SORT_MENU:
m_Banner.LoadSort();
break;
case SORT_MODE_MENU:
m_Banner.LoadMode();
break;
}
m_sSampleMusicToPlay = THEME->GetPathS(m_sName,"sort music");
break;
default:
ASSERT(0);
}
m_sprBalloon.StopTweening();
COMMAND( m_sprBalloon, "Hide" );
m_sprCourseHasMods->StopTweening();
COMMAND( m_sprCourseHasMods, "Hide" );
}
break;
case TYPE_SONG:
case TYPE_PORTAL:
{
m_sSampleMusicToPlay = pSong->GetMusicPath();
m_pSampleMusicTimingData = &pSong->m_Timing;
m_fSampleStartSeconds = pSong->m_fMusicSampleStartSeconds;
m_fSampleLengthSeconds = pSong->m_fMusicSampleLengthSeconds;
m_textNumSongs.SetText( ssprintf("%d", SongManager::GetNumStagesForSong(pSong) ) );
m_textTotalTime.SetText( SecondsToMMSSMsMs(pSong->m_fMusicLengthSeconds) );
pSong->GetSteps( m_vpSteps, GAMESTATE->GetCurrentStyle()->m_StepsType );
StepsUtil::SortNotesArrayByDifficulty( m_vpSteps );
if ( PREFSMAN->m_bShowBanners )
g_sBannerPath = pSong->GetBannerPath();
if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() )
{
m_BPMDisplay.CycleRandomly();
}
else
{
m_BPMDisplay.SetBPM( pSong );
}
g_sCDTitlePath = pSong->GetCDTitlePath();
g_bWantFallbackCdTitle = true;
const vector<Song*> best = SONGMAN->GetBestSongs( PROFILE_SLOT_MACHINE );
const int index = FindIndex( best.begin(), best.end(), pSong );
if( index != -1 )
m_MachineRank.SetText( ssprintf("%i", index+1) );
m_DifficultyDisplay.SetDifficulties( pSong, GAMESTATE->GetCurrentStyle()->m_StepsType );
SwitchToPreferredDifficulty();
/* Short delay before actually showing these, so they don't show
* up when scrolling fast. It'll still show up in "slow" scrolling,
* but it doesn't look at weird as it does in "fast", and I don't
* like the effect with a lot of delay. */
if( pSong->m_fMusicLengthSeconds > PREFSMAN->m_fMarathonVerSongSeconds )
{
m_sprBalloon.StopTweening();
m_sprBalloon.Load( THEME->GetPathG(m_sName,"balloon marathon") );
SET_XY( m_sprBalloon );
COMMAND( m_sprBalloon, "Show" );
}
else if( pSong->m_fMusicLengthSeconds > PREFSMAN->m_fLongVerSongSeconds )
{
m_sprBalloon.StopTweening();
m_sprBalloon.Load( THEME->GetPathG(m_sName,"balloon long") );
SET_XY( m_sprBalloon );
COMMAND( m_sprBalloon, "Show" );
}
else
{
m_sprBalloon.StopTweening();
COMMAND( m_sprBalloon, "Hide" );
}
m_sprCourseHasMods->StopTweening();
COMMAND( m_sprCourseHasMods, "Hide" );
m_Artists.push_back( pSong->GetDisplayArtist() );
m_AltArtists.push_back( pSong->GetTranslitArtist() );
}
break;
case TYPE_ROULETTE:
case TYPE_RANDOM:
bWantBanner = false; /* we load it ourself */
switch(m_MusicWheel.GetSelectedType())
{
case TYPE_ROULETTE: m_Banner.LoadRoulette(); break;
case TYPE_RANDOM: m_Banner.LoadRandom(); break;
default: ASSERT(0);
}
m_BPMDisplay.NoBPM();
g_sCDTitlePath = ""; // none
m_DifficultyDisplay.UnsetDifficulties();
m_fSampleStartSeconds = 0;
m_fSampleLengthSeconds = -1;
m_textNumSongs.SetText( "" );
m_textTotalTime.SetText( "" );
switch( m_MusicWheel.GetSelectedType() )
{
case TYPE_ROULETTE:
m_sSampleMusicToPlay = THEME->GetPathS(m_sName,"roulette music");
break;
case TYPE_RANDOM:
m_sSampleMusicToPlay = THEME->GetPathS(m_sName,"random music");
break;
default:
ASSERT(0);
}
m_sprBalloon.StopTweening();
COMMAND( m_sprBalloon, "Hide" );
m_sprCourseHasMods->StopTweening();
COMMAND( m_sprCourseHasMods, "Hide" );
break;
case TYPE_COURSE:
{
Course* pCourse = m_MusicWheel.GetSelectedCourse();
StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType;
Trail *pTrail = pCourse->GetTrail( st );
ASSERT( pTrail );
pCourse->GetTrails( m_vpTrails, GAMESTATE->GetCurrentStyle()->m_StepsType );
m_sSampleMusicToPlay = THEME->GetPathS(m_sName,"course music");
m_fSampleStartSeconds = 0;
m_fSampleLengthSeconds = -1;
m_textNumSongs.SetText( ssprintf("%d", pCourse->GetEstimatedNumStages()) );
float fTotalSeconds;
if( pCourse->GetTotalSeconds(st,fTotalSeconds) )
m_textTotalTime.SetText( SecondsToMMSSMsMs(fTotalSeconds) );
else
m_textTotalTime.SetText( "xx:xx.xx" ); // The numbers format doesn't have a '?'. Is there a better solution?
g_sBannerPath = pCourse->m_sBannerPath;
if( g_sBannerPath.empty() )
m_Banner.LoadFallback();
m_BPMDisplay.SetBPM( pCourse );
m_DifficultyDisplay.UnsetDifficulties();
SwitchToPreferredDifficulty();
FOREACH_CONST( TrailEntry, pTrail->m_vEntries, e )
{
if( e->bMystery )
{
m_Artists.push_back( "???" );
m_AltArtists.push_back( "???" );
} else {
m_Artists.push_back( e->pSong->GetDisplayArtist() );
m_AltArtists.push_back( e->pSong->GetTranslitArtist() );
}
}
const vector<Course*> best = SONGMAN->GetBestCourses( PROFILE_SLOT_MACHINE );
const int index = FindCourseIndexOfSameMode( best.begin(), best.end(), pCourse );
if( index != -1 )
m_MachineRank.SetText( ssprintf("%i", index+1) );
m_sprBalloon.StopTweening();
COMMAND( m_sprBalloon, "Hide" );
if( pCourse->HasMods() )
{
m_sprCourseHasMods->StopTweening();
COMMAND( m_sprCourseHasMods, "Show" );
}
else
{
m_sprCourseHasMods->StopTweening();
COMMAND( m_sprCourseHasMods, "Hide" );
}
break;
}
default:
ASSERT(0);
}
m_sprCDTitleFront.UnloadTexture();
m_sprCDTitleBack.UnloadTexture();
if( m_MusicWheel.GetSelectedType() == TYPE_ROULETTE || m_MusicWheel.GetSelectedType() == TYPE_RANDOM )
m_BackgroundLoader.Abort();
g_bCDTitleWaiting = false;
if( !g_sCDTitlePath.empty() || g_bWantFallbackCdTitle )
{
LOG->Trace( "cache \"%s\"", g_sCDTitlePath.c_str());
m_BackgroundLoader.CacheFile( g_sCDTitlePath ); // empty OK
g_bCDTitleWaiting = true;
}
g_bBannerWaiting = false;
if( bWantBanner )
{
LOG->Trace("LoadFromCachedBanner(%s)",g_sBannerPath .c_str());
if( m_Banner.LoadFromCachedBanner( g_sBannerPath ) )
{
m_BackgroundLoader.CacheFile( g_sBannerPath );
g_bBannerWaiting = true;
}
}
// Don't stop music if it's already playing the right file.
g_bSampleMusicWaiting = false;
if( !m_MusicWheel.IsRouletting() && SOUND->GetMusicPath() != m_sSampleMusicToPlay )
{
SOUND->StopMusic();
if( !m_sSampleMusicToPlay.empty() )
g_bSampleMusicWaiting = true;
}
g_StartedLoadingAt.Touch();
// update stage counter display (long versions/marathons)
m_sprStage.Load( THEME->GetPathG(m_sName,"stage "+GAMESTATE->GetStageText()) );
m_Artist.SetTips( m_Artists, m_AltArtists );
FOREACH_HumanPlayer( p )
{
if( GAMESTATE->m_pCurCourse )
AfterTrailChange( p );
else
AfterStepsChange( p );
}
switch( m_MusicWheel.GetSelectedType() )
{
case TYPE_COURSE:
m_CourseContentsFrame.TweenInAfterChangedCourse();
break;
}
}
void ScreenSelectMusic::UpdateOptionsDisplays()
{
// m_OptionIcons.Load( GAMESTATE->m_PlayerOptions, &GAMESTATE->m_SongOptions );
// m_PlayerOptionIcons.Refresh();
FOREACH_PlayerNumber( p )
{
if( GAMESTATE->IsHumanPlayer(p) )
{
m_OptionIconRow[p].Refresh();
CString s = GAMESTATE->m_PlayerOptions[p].GetString();
s.Replace( ", ", "\n" );
// m_textPlayerOptions[p].SetText( s );
}
}
CString s = GAMESTATE->m_SongOptions.GetString();
s.Replace( ", ", "\n" );
m_textSongOptions.SetText( s );
}
void ScreenSelectMusic::SortOrderChanged()
{
m_MusicSortDisplay.Set( GAMESTATE->m_SortOrder );
switch( GAMESTATE->m_SortOrder )
{
case SORT_ALL_COURSES:
case SORT_NONSTOP_COURSES:
case SORT_ONI_COURSES:
case SORT_ENDLESS_COURSES:
case SORT_SORT_MENU:
case SORT_MODE_MENU:
// do nothing
break;
default:
if( SHOW_DIFFICULTY_LIST )
m_DifficultyList.Show();
break;
}
// tween music sort on screen
// m_MusicSortDisplay.FadeOn( 0, "fade", TWEEN_TIME );
}
/*
* (c) 2001-2004 Chris Danford
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/