diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index 77051e30f8..4a91154957 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -26,6 +26,11 @@ ________________________________________________________________________________ * [Loading] The song length from the cache is used now. This cut loading time by 30% for kyz. [kyzentun] +2015/03/02 +---------- +* [Song] Per-chart music feature added. Each chart can have a MUSIC tag. + [kyzentun] + 2015/03/01 ---------- * [SelectMusic] Select Music now plays music file named by #PREVIEW tag if diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 6c7d0c6fac..dbba7be85d 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -1124,6 +1124,7 @@ Modify Attacks at current beat=Modify attacks at current beat MoveRandomToEnd=Random At End More Options=More Options MovieColorDepth=Movie Color +Music File=Music File MusicWheelSwitchSpeed=Wheel Speed MusicWheelUsesSections=Wheel Sections Network Options=Network Options @@ -1527,6 +1528,7 @@ Enter a new preview start.=Enter when the music sample starts. Enter a new preview length.=Enter how long the music sample lasts. Enter a new min BPM.=Enter the minimum displayed BPM. Enter a new max BPM.=Enter the maximum displayed BPM. +Enter the music file for this chart.=Enter the music file for this chart. Leave blank to use the song music. Enter the new track mapping.=Enter the new track mapping. Entry %d, '%d', is out of range 1 to %d.=Entry %d, '%d', is out of range 1 to %d. More than %d notes per measure is not allowed. This change has been reverted.=More than %d notes per measure is not allowed. This change has been reverted. @@ -2013,6 +2015,7 @@ The chart name cannot contain any of the following characters: %s=The chart name The edit name cannot contain any of the following characters: %s=The edit name can not contain any of the following characters: %s The step author's name cannot contain any of the following characters: %s=The step author's name cannot contain any of the following characters: %s The preview file '%s' does not exist.=The preview file '%s' does not exist. +The music file '%s' does not exist.=The music file '%s' does not exist. [SongSort] FewestPlays=Fewest Plays diff --git a/src/AutoKeysounds.cpp b/src/AutoKeysounds.cpp index 8c6c59f857..9789059c14 100644 --- a/src/AutoKeysounds.cpp +++ b/src/AutoKeysounds.cpp @@ -127,7 +127,7 @@ void AutoKeysounds::LoadTracks( const Song *pSong, RageSoundReader *&pShared, Ra pShared = NULL; vector vsMusicFile; - const RString sMusicPath = pSong->GetMusicPath(); + const RString sMusicPath = GAMESTATE->m_pCurSteps[GAMESTATE->GetMasterPlayerNumber()]->GetMusicPath(); if( !sMusicPath.empty() ) vsMusicFile.push_back( sMusicPath ); diff --git a/src/GameState.cpp b/src/GameState.cpp index b3d91d9b90..84dcfdb74f 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -1077,6 +1077,75 @@ void GameState::ForceSharedSidesMatch() } } +void GameState::ForceOtherPlayersToCompatibleSteps(PlayerNumber main) +{ + if(IsCourseMode()) + { + Trail* steps_to_match= m_pCurTrail[main].Get(); + if(steps_to_match == NULL) { return; } + int num_players= GAMESTATE->GetNumPlayersEnabled(); + StyleType styletype_to_match= GAMEMAN->GetFirstCompatibleStyle( + GAMESTATE->GetCurrentGame(), num_players, steps_to_match->m_StepsType) + ->m_StyleType; + FOREACH_EnabledPlayer(pn) + { + Trail* pn_steps= m_pCurTrail[pn].Get(); + bool match_failed= false; + if(steps_to_match != pn_steps && pn_steps != NULL) + { + StyleType pn_styletype= GAMEMAN->GetFirstCompatibleStyle( + GAMESTATE->GetCurrentGame(), num_players, pn_steps->m_StepsType) + ->m_StyleType; + if(styletype_to_match == StyleType_TwoPlayersSharedSides || + pn_styletype == StyleType_TwoPlayersSharedSides) + { + match_failed= true; + } + + if(match_failed) + { + m_pCurTrail[pn].Set(steps_to_match); + } + } + } + } + else + { + Steps* steps_to_match= m_pCurSteps[main].Get(); + if(steps_to_match == NULL) { return; } + int num_players= GAMESTATE->GetNumPlayersEnabled(); + StyleType styletype_to_match= GAMEMAN->GetFirstCompatibleStyle( + GAMESTATE->GetCurrentGame(), num_players, steps_to_match->m_StepsType) + ->m_StyleType; + RString music_to_match= steps_to_match->GetMusicFile(); + FOREACH_EnabledPlayer(pn) + { + Steps* pn_steps= m_pCurSteps[pn].Get(); + bool match_failed= false; + if(steps_to_match != pn_steps && pn_steps != NULL) + { + StyleType pn_styletype= GAMEMAN->GetFirstCompatibleStyle( + GAMESTATE->GetCurrentGame(), num_players, pn_steps->m_StepsType) + ->m_StyleType; + if(styletype_to_match == StyleType_TwoPlayersSharedSides || + pn_styletype == StyleType_TwoPlayersSharedSides) + { + match_failed= true; + } + if(music_to_match != pn_steps->GetMusicFile()) + { + match_failed= true; + } + + if(match_failed) + { + m_pCurSteps[pn].Set(steps_to_match); + } + } + } + } +} + void GameState::Update( float fDelta ) { m_SongOptions.Update( fDelta ); @@ -2602,6 +2671,7 @@ public: Steps *pS = Luna::check(L,2); SetCompatibleStyleOrError(p, L, pS->m_StepsType, pn); p->m_pCurSteps[pn].Set(pS); + p->ForceOtherPlayersToCompatibleSteps(pn); } COMMON_RETURN_SELF; } @@ -2632,6 +2702,7 @@ public: Trail *pS = Luna::check(L,2); SetCompatibleStyleOrError(p, L, pS->m_StepsType, pn); p->m_pCurTrail[pn].Set(pS); + p->ForceOtherPlayersToCompatibleSteps(pn); } COMMON_RETURN_SELF; } diff --git a/src/GameState.h b/src/GameState.h index 0001dd15f1..929822b625 100644 --- a/src/GameState.h +++ b/src/GameState.h @@ -76,6 +76,7 @@ public: bool CanSafelyEnterGameplay(RString& reason); void SetCompatibleStylesForPlayers(); void ForceSharedSidesMatch(); + void ForceOtherPlayersToCompatibleSteps(PlayerNumber main); void Update( float fDelta ); diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index ca647b5c55..e5ad98574e 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -727,6 +727,11 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach pNewNotes->SetFilename(sPath); out.AddSteps( pNewNotes ); } + + else if(sValueName == "MUSIC") + { + pNewNotes->SetMusicFile(sParams[1]); + } else if( sValueName=="BPMS" ) { diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 9e373edbe9..aadfb9cdc1 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -362,6 +362,12 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa lines.push_back( ssprintf( "#DIFFICULTY:%s;", DifficultyToString(in.GetDifficulty()).c_str() ) ); lines.push_back( ssprintf( "#METER:%d;", in.GetMeter() ) ); + const RString& music= in.GetMusicFile(); + if(!music.empty()) + { + lines.push_back(ssprintf("#MUSIC:%s;", music.c_str())); + } + vector asRadarValues; FOREACH_PlayerNumber( pn ) { diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index 32ee766466..d8e7106aaa 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -106,6 +106,7 @@ AutoScreenMessage( SM_BackFromSpeedWaitChange ); AutoScreenMessage( SM_BackFromSpeedModeChange ); AutoScreenMessage( SM_BackFromScrollChange ); AutoScreenMessage( SM_BackFromFakeChange ); +AutoScreenMessage( SM_BackFromStepMusicChange ); AutoScreenMessage( SM_DoEraseStepTiming ); AutoScreenMessage( SM_DoSaveAndExit ); AutoScreenMessage( SM_DoExit ); @@ -915,7 +916,9 @@ static MenuDef g_StepsInformation( true, EditMode_Full, true, true, 0, NULL ), MenuRowDef(ScreenEdit::step_max_bpm, "Max BPM", - true, EditMode_Full, true, true, 0, NULL ) + true, EditMode_Full, true, true, 0, NULL ), + MenuRowDef(ScreenEdit::step_music, + "Music File", true, EditMode_Full,true, true, 0, NULL) ); static MenuDef g_StepsData( @@ -2343,6 +2346,9 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) int(vSteps.size()) ); SCREENMAN->SystemMessage( s ); m_soundSwitchSteps.Play(true); + // Reload the music because it can be different for every steps. -Kyz + m_AutoKeysounds.FinishLoading(); + m_pSoundMusic = m_AutoKeysounds.GetSound(); ScrollTo( GetAppropriateTiming().GetBeatFromElapsedTime(curSecond) ); } @@ -3663,7 +3669,12 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) SetDirty( true ); } } - + else if ( SM == SM_BackFromStepMusicChange && !ScreenTextEntry::s_bCancelledLast ) + { + // Reload the music because it just changed. -Kyz + m_AutoKeysounds.FinishLoading(); + m_pSoundMusic = m_AutoKeysounds.GetSound(); + } else if( SM == SM_BackFromBGChange ) { HandleBGChangeChoice( (BGChangeChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers ); @@ -4300,6 +4311,12 @@ static void ChangeStepMeter( const RString &sNew ) GAMESTATE->m_pCurSteps[PLAYER_1]->SetMeter(max(diff, 1)); } +static void ChangeStepMusic(const RString& sNew) +{ + Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + pSteps->SetMusicFile(sNew); +} + static void ChangeMainTitle( const RString &sNew ) { Song* pSong = GAMESTATE->m_pCurSong; @@ -4589,6 +4606,8 @@ void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &iAns g_StepsInformation.rows[step_display_bpm].iDefaultChoice = pSteps->GetDisplayBPM(); g_StepsInformation.rows[step_min_bpm].SetOneUnthemedChoice( FloatToString(pSteps->GetMinBPM())); g_StepsInformation.rows[step_max_bpm].SetOneUnthemedChoice( FloatToString(pSteps->GetMaxBPM())); + g_StepsInformation.rows[step_music].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full); + g_StepsInformation.rows[step_music].SetOneUnthemedChoice( pSteps->GetMusicFile() ); EditMiniMenu( &g_StepsInformation, SM_BackFromStepsInformation, SM_None ); } break; @@ -5358,6 +5377,7 @@ static LocalizedString ENTER_NEW_STEP_AUTHOR( "ScreenEdit", "Enter the author wh static LocalizedString ENTER_NEW_METER( "ScreenEdit", "Enter a new meter." ); static LocalizedString ENTER_MIN_BPM ("ScreenEdit","Enter a new min BPM."); static LocalizedString ENTER_MAX_BPM ("ScreenEdit","Enter a new max BPM."); +static LocalizedString ENTER_NEW_STEP_MUSIC("ScreenEdit", "Enter the music file for this chart."); void ScreenEdit::HandleStepsInformationChoice( StepsInformationChoice c, const vector &iAnswers ) { Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; @@ -5438,6 +5458,17 @@ void ScreenEdit::HandleStepsInformationChoice( StepsInformationChoice c, const v ChangeStepsMaxBPM, NULL); break; } + case step_music: + { + ScreenTextEntry::TextEntry(SM_BackFromStepMusicChange, + ENTER_NEW_STEP_MUSIC, + m_pSteps->GetMusicFile(), + 255, + SongUtil::ValidateCurrentStepsMusic, + ChangeStepMusic, + NULL); + break; + } default: break; } diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index 51832b79f7..dc828d578b 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -537,6 +537,7 @@ public: step_display_bpm, step_min_bpm, step_max_bpm, + step_music, NUM_STEPS_INFORMATION_CHOICES }; void HandleStepsInformationChoice( StepsInformationChoice c, const vector &iAnswers ); diff --git a/src/ScreenSelectMusic.cpp b/src/ScreenSelectMusic.cpp index 67793d059f..29ea05f7f1 100644 --- a/src/ScreenSelectMusic.cpp +++ b/src/ScreenSelectMusic.cpp @@ -1039,6 +1039,7 @@ void ScreenSelectMusic::ChangeSteps( PlayerNumber pn, int dir ) m_soundDifficultyHarder.SetProperty( "Pan", fBalance ); m_soundDifficultyHarder.PlayCopy(); } + GAMESTATE->ForceOtherPlayersToCompatibleSteps(pn); Message msg( "ChangeSteps" ); msg.SetParam( "Player", pn ); diff --git a/src/Song.cpp b/src/Song.cpp index 28268eaa35..ee9071d74c 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -1417,7 +1417,7 @@ vector Song::GetInstrumentTracksToVectorString() const return ret; } -RString GetSongAssetPath( RString sPath, const RString &sSongPath ) +RString Song::GetSongAssetPath( RString sPath, const RString &sSongPath ) { if( sPath == "" ) return RString(); diff --git a/src/Song.h b/src/Song.h index 212219c2e5..655f2e584f 100644 --- a/src/Song.h +++ b/src/Song.h @@ -248,6 +248,7 @@ public: AttackArray m_Attacks; vector m_sAttackString; + static RString GetSongAssetPath( RString sPath, const RString &sSongPath ); RString GetMusicPath() const; RString GetInstrumentTrackPath( InstrumentTrack it ) const; RString GetBannerPath() const; diff --git a/src/SongUtil.cpp b/src/SongUtil.cpp index 5827f5abb9..3e17164735 100644 --- a/src/SongUtil.cpp +++ b/src/SongUtil.cpp @@ -917,6 +917,24 @@ bool SongUtil::ValidateCurrentSongPreview(const RString& answer, RString& error) return valid; } +static LocalizedString MUSIC_DOES_NOT_EXIST("SongUtil", "The music file '%s' does not exist."); +bool SongUtil::ValidateCurrentStepsMusic(const RString &answer, RString &error) +{ + if(answer.empty()) + return true; + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + RString real_file= pSteps->GetMusicFile(); + pSteps->SetMusicFile(answer); + RString path= pSteps->GetMusicPath(); + bool valid= DoesFileExist(path); + pSteps->SetMusicFile(real_file); + if(!valid) + { + error= ssprintf(MUSIC_DOES_NOT_EXIST.GetValue(), answer.c_str()); + } + return valid; +} + void SongUtil::GetAllSongGenres( vector &vsOut ) { set genres; diff --git a/src/SongUtil.h b/src/SongUtil.h index 827f86812e..fa2f6b124d 100644 --- a/src/SongUtil.h +++ b/src/SongUtil.h @@ -166,6 +166,7 @@ namespace SongUtil bool ValidateCurrentStepsCredit( const RString &sAnswer, RString &sErrorOut ); bool ValidateCurrentStepsChartName(const RString &answer, RString &error); bool ValidateCurrentSongPreview(const RString& answer, RString& error); + bool ValidateCurrentStepsMusic(const RString &answer, RString &error); void GetAllSongGenres( vector &vsOut ); /** diff --git a/src/Steps.cpp b/src/Steps.cpp index 0dbc71c05c..d8847deb24 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -589,6 +589,23 @@ bool Steps::HasSignificantTimingChanges() const return false; } +const RString Steps::GetMusicPath() const +{ + return Song::GetSongAssetPath( + m_MusicFile.empty() ? m_pSong->m_sMusicFile : m_MusicFile, + m_pSong->GetSongDir()); +} + +const RString& Steps::GetMusicFile() const +{ + return m_MusicFile; +} + +void Steps::SetMusicFile(const RString& file) +{ + m_MusicFile= file; +} + void Steps::SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] ) { DeAutogen(); diff --git a/src/Steps.h b/src/Steps.h index 05440011b6..15fd7f882a 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -173,6 +173,10 @@ public: * @return true if it does, or false otherwise. */ bool HasAttacks() const; + const RString GetMusicPath() const; // Returns the path for loading. + const RString& GetMusicFile() const; // Returns the filename for the simfile. + void SetMusicFile(const RString& file); + // Lua void PushSelf( lua_State *L ); @@ -218,7 +222,9 @@ private: /** @brief The name of the file where these steps are stored. */ RString m_sFilename; /** @brief true if these Steps were loaded from or saved to disk. */ - bool m_bSavedToDisk; + bool m_bSavedToDisk; + /** @brief allows the steps to specify their own music file. */ + RString m_MusicFile; /** @brief What profile was used? This is ProfileSlot_Invalid if not from a profile. */ ProfileSlot m_LoadedFromProfile;